# 設計模式: SOLID
## S (Single Responsibility Principle; 單一責任原則)
* 盡量一個小積木負責執行單一任務,不要做太多。
* 舉例 (影像分割)
```python=
def segment_image_and_export_boolean_mask(image_rgb):
image_gray = rgb2gray(image_rgb)
boolean_mask = segmenter(image_gray)
mask_to_save = boolean_mask.astype(np.uint8) * 255
cv2.imwrite('boolean_mask.png', mask_to_save)
```
上述先透過`rgb2gray`把影像轉灰階; 然後再丟給`segmenter`做分割。最後將分割後的結果儲存。
* 缺點是什麼?
* 如何改寫?
## O (Open/Close Principle; 開/閉原則)
* **Open** for extension, **Close** for modification (對於拓展開放, 但對於更改封閉)
* 要追加新功能的時候,不會改到既有的類/函數。
* 若要更改某實作,只要改動某一個小類/函數。不會需要一次改好幾個。
* 拿上述Single Responsibility Principle的範例來給例子:
* ...
## L (Liskov Substitution Principle; 里氏替換原則)
1. 用戶通常預期 scikit-learn的 分類器 物件,皆具有 `fit(train_x, train_y, ...)`這個方法。
因此一拿到`model`物件時,應可執行 `model.fit(train_x, train_y)`。而不是有時候會變成必須呼叫 `model.learn(train_y, train_x)`。(難以使用,介面混亂。)
```python=
from sklearn.tree import DecisionTreeClassifier
from sklearn.linear_model import LogisticRegression
from sklearn import datasets
# 加載數據
iris = datasets.load_iris()
x_train = iris.data
y_train = iris.target
# 訓練樹模型
model = DecisionTreeClassifier()
model.fit(x_train, y_train)
# 訓練羅吉斯模型
model = LogisticRegression()
model.fit(x_train, y_train)
```
2. 若某函數吃某父類為輸入參數,該父類的所有子類必然也可當作某函數的輸入參數。
```python=
class Animal:
pass
class Cow(Animal):
pass
class Pig(Animal):
pass
class Vegitable:
pass
def eat(something: Animal):
print(f"Animal: {something.__class__.__name__} is eaten.")
eat(Cow()) # 輸出: Animal: Cow is eaten.
eat(Pig()) # 輸出: Animal: Pig is eaten.
```
## I (Interface Segregation Principle; 介面隔離原則)
* 不好的介面設計:
```python=
class MultiFunctionalPrinter:
def print_document(self, document):
pass
def scan_document(self, document):
pass
def fax_document(self, document):
pass
```
在此設計中,印表機都是此`MultiFunctionalPrinter`的子類。那,若是某印表機不支持掃描功能。那它就額外繼承了一些不必要的方法(例如`scan_document`)了。
* 較好的介面設計:
```python=
class Printer:
def print_document(self, document):
pass
class Scanner:
def scan_document(self, document):
pass
class FaxMachine:
def fax_document(self, document):
pass
class SimplePrinter(Printer):
def print_document(self, document):
# 實現打印功能
pass
class MultiFunctionPrinter(Printer, Scanner, FaxMachine):
def print_document(self, document):
# 實現打印功能
pass
def scan_document(self, document):
# 實現掃描功能
pass
def fax_document(self, document):
# 實現傳真功能
pass
```
問: 為何比較好呢? 答: ____________。
## D (Dependency Inversion Principle; 依賴反轉原則)
* 概念: 高層模塊不應該依賴於具體細節。應依賴於抽象。
* 範例:
```python=
from abc import ABC, abstractmethod
# 抽象
class MessageSender(ABC):
@abstractmethod
def send(self, message):
pass
# 特定的具體實現 (Email寄送器)
class EmailSender(MessageSender):
def send(self, message):
print(f"Sending email: {message}")
# 特定的具體實現 (SMS寄送器)
class SMSSender(MessageSender):
def send(self, message):
print(f"Sending SMS: {message}")
# 高層模塊
class Notification:
def __init__(self, sender: MessageSender):
self.sender = sender
def notify(self, message):
self.sender.send(message)
# 使用
email_sender = EmailSender()
notification = Notification(email_sender)
notification.notify("Hello World!")
```
上述範例中,高層模塊僅依賴抽象的`MessageSender`。高層模塊並無依賴特定的寄送信息器(例如Email寄送器, SMS寄送器等)。如此,可減少高層模塊被無端改動的機會。