繼上篇的物件導向程式設計概念,近期在面試上有遇到一個很重要的相關觀念,
當時不知道為什麼,現在整理出來分享~
:有聽過 SOLID 嗎?
:SOLID 是什麼?
SOLID 是什麼?
SOLID 是一組五個物件導向程式設計的基本原則,旨在幫助開發者創建可維護、可擴展且易於理解的程式碼。這些原則有助於確保軟體設計具有高內聚性和低耦合性,這對於長期維護和協作開發非常重要。
單一職責原則 (Single Responsibility Principle - SRP)
- 一個模組應該只對唯一的一個角色負責。
- 通過將不同的職責分開,我們可以輕鬆地修改和擴展每個類別,而不會影響其他程式碼。
假設我們有一個名為
Employee
的類別,
表示公司的員工,我們將根據 SRP 創建兩個具有不同職責的類別。- EmployeeInfo 類別(負責員工的基本資訊):
1
2
3
4
5
6
7
8
9
10
11
12class EmployeeInfo
attr_reader :name, :employee_id
def initialize(name, employee_id)
@name = name
@employee_id = employee_id
end
def display_info
"Name: #{@name}, Employee ID: #{@employee_id}"
end
end- EmployeeSalary 類別(負責員工的薪水計算):
1
2
3
4
5
6
7
8
9
10
11
12class EmployeeSalary
attr_reader :employee, :salary
def initialize(employee, salary)
@employee = employee
@salary = salary
end
def calculate_bonus
@salary * 0.1 # 假設獎金是薪水的10%
end
end以上兩個不同的類別,每個類別都具有不同的職責。
EmployeeInfo
負責管理員工的基本資訊(如姓名和員工ID)EmployeeSalary
負責計算員工的薪水和獎金。
這兩個類別各自專注於其單一職責,符合 SRP 的原則。
attr_reader
是 Ruby 中一個用於自動生成讀取實例變數(instance variables)的方法的簡便方式。主要用途是使類別的實例變數能夠被外部程式碼讀取,而不需要額外編寫自訂的 getter 方法。attr_reader
通常用於創建只讀取(read-only)的實例變數,這意味著外部程式碼可以訪問變數的值,但不能修改它。
以下是attr_reader
的基本用法:1
2
3
4
5
6
7class MyClass
attr_reader :my_variable
def initialize(value)
@my_variable = value
end
end在這個示例中,
attr_reader :my_variable
被用來定義一個名為my_variable
的實例變數,並自動生成了一個名為my_variable
的讀取方法(getter 方法)。這意味著我們可以在外部程式碼中訪問my_variable
的值,如下所示:1
2obj = MyClass.new(42)
puts obj.my_variable # 輸出: 42需要注意的是,
attr_reader
只創建了讀取方法,不允許外部程式碼修改實例變數的值。如果你需要允許變數被修改,則可以使用attr_writer
或attr_accessor
,分別用於生成寫入方法(setter 方法)或同時生成讀取和寫入方法。
- 開放-封閉原則 (Open-Closed Principle - OCP)
- 這個原則強調軟體實體(如類別、模組、函數等)應該是開放擴展的,但封閉修改的。
- 可以擴展現有的功能,而不必修改現有的程式碼。通常,這可以通過使用抽象化和多型來實現。
可以往前篇回顧抽象化和多態性範例:Day 13 - 理解 Ruby - 物件導向程式設計語言
里氏替換原則 (Liskov Substitution Principle - LSP)
- 這個原則強調子類別應該能夠替換其基類別,而不會破壞程式的正確性。換句話說,如果某個程式使用基類別,則應該可以安全地替換為其子類別,而不會導致不正確的行為。
假設我們有一個基類別
Bird
,代表鳥類,並且有一個fly
方法用來表示鳥類飛行的行為:1
2
3
4
5class Bird
def fly
"This bird can fly."
end
end然後,我們建立了一個子類別
Penguin
,代表企鵝,但企鵝不能飛行,所以我們需要重寫fly
方法:1
2
3
4
5class Penguin < Bird
def fly
"This penguin can't fly."
end
end現在,我們可以使用里氏替換原則,確保子類別
Penguin
可以替換其基類別Bird
,並且在不影響程式正確性的情況下重寫fly
方法。1
2
3
4
5bird = Bird.new
penguin = Penguin.new
puts bird.fly # 輸出: "This bird can fly."
puts penguin.fly # 輸出: "This penguin can't fly."雖然
Penguin
重寫了fly
方法,但它仍然可以替換Bird
並遵守里氏替換原則。這使得我們能夠根據需要擴展程式碼,同時保持對基類別的一致性使用,而不會破壞程式的正確性。
介面隔離原則 (Interface Segregation Principle - ISP)
- 這個原則建議將一個大型的、具有多個方法的介面分解成多個小型的、專注於特定用途的介面。這樣可以避免實現不需要的方法,從而減少類別之間的耦合性,並提高代碼的可讀性。
假設我們正在建立一個文件系統應用程式,需要處理文件(File)和資料夾(Folder)。我們想確保這些元素都可以執行基本的操作,如開啟(
open
)、關閉(close
)和刪除(delete
)。我們可以創建兩個模組,分別是
Openable
和Deletable
:1
2
3
4
5
6
7
8
9
10
11module Openable
def open
puts "Opening..."
end
end
module Deletable
def delete
puts "Deleting..."
end
end接下來,我們創建文件(File)和資料夾(Folder)類別,並混入相應的模組:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25class File
include Openable
include Deletable
def initialize(name)
@name = name
end
def name
@name
end
end
class Folder
include Deletable
def initialize(name)
@name = name
@contents = []
end
def add_item(item)
@contents << item
end
end在以上例子中,
File
和Folder
類別分別混入了Openable
和Deletable
模組。這樣,File
可以執行開啟、關閉和刪除操作,而Folder
只能執行刪除操作。 確保每個類別僅實現了他們需要的方法,而不包含不必要的方法,從而減少不必要的耦合。 如果未來需要添加新的操作,可以輕鬆擴展相應的模組,而不會影響其他部分的程式碼。依賴反轉原則 (Dependency Inversion Principle - DIP)
- 這個原則強調高層次模組不應該依賴於低層次模組,兩者都應該依賴於抽象。此外,抽象不應該依賴於具體實現,而具體實現應該依賴於抽象。這可以通過使用介面或抽象類別來實現。
假設我們有一個介面
Device
代表設備,並且我們希望能夠將不同類型的設備連接到應用程式。
設備可以執行connect
操作。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18# 設備介面
class Device
def connect
end
end
# 具體設備類別
class Keyboard < Device
def connect
puts "Keyboard connected."
end
end
class Mouse < Device
def connect
puts "Mouse connected."
end
end我們定義了一個
Device
類別,並建立兩個具體的設備類別Keyboard
和Mouse
,分別覆蓋了connect
方法以提供實際的連接功能。這樣,當我們需要添加新的設備類別時,只需創建一個新的設備類別,覆蓋
connect
方法,這種方式可以實現 DIP,因為高層模組(使用這些設備的程式碼)依賴於Device
抽象介面,而不需要關心具體的設備類別。
SOLID 原則有助於創建有彈性、可擴展和易於維護的程式碼。這些原則通常與設計模式和良好的軟體設計實踐一起使用,以實現高品質的軟體。當設計和編寫程式碼時,考慮這些原則可以幫助避免常見的設計問題並提高代碼的質量。
但是,即便這樣簡短的講完這些內容,還是必須反覆去複習才會理解!
今天先分享至此,我們下篇待續~!
參考資料:
➫ 物件導向程式設計基本原則 - SOLID
➫ J.J.’s Blogs