# 設計模式: 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寄送器等)。如此,可減少高層模塊被無端改動的機會。