Python編程應用設計原則詳解
寫出能用的代碼很簡單,寫出好用的代碼很難。
好用的代碼,也都會遵循一此原則,這就是設計原則,它們分別是:
- 單一職責原則 (SRP)
- 開閉原則 (OCP)
- 裡氏替換原則 (LSP)
- 接口隔離原則 (ISP)
- 依賴倒置原則 (DIP)
提取這五種原則的首字母縮寫詞,就是 SOLID 原則。下面分別進行介紹,並展示如何在 Python 中應用。
1、單一職責原則 SRP
單一職責原則(Single Responsibility Principle)這個原則的英文描述是這樣的:A class or module should have a single responsibility。如果我們把它翻譯成中文,那就是:一個類或者模塊隻負責完成一個職責(或者功能)。
讓我們舉一個更簡單的例子,我們有一個數字 L = [n1, n2, …, nx] 的列表,我們計算一些數學函數。例如,計算最大值、平均值等。
一個不好的方法是讓一個函數來完成所有的工作:
import numpy as np def math_operations(list_): # Compute Average print(f"the mean is {np.mean(list_)}") # Compute Max print(f"the max is {np.max(list_)}") math_operations(list_ = [1,2,3,4,5]) # the mean is 3.0 # the max is 5
實際開發中,你可以認為 math_operations 很龐大,揉雜瞭各種功能代碼。
為瞭使這個更符合單一職責原則,我們應該做的第一件事是將函數 math_operations 拆分為更細粒度的函數,一個函數隻幹一件事:
def get_mean(list_): '''Compute Max''' print(f"the mean is {np.mean(list_)}") def get_max(list_): '''Compute Max''' print(f"the max is {np.max(list_)}") def main(list_): # Compute Average get_mean(list_) # Compute Max get_max(list_) main([1,2,3,4,5]) # the mean is 3.0 # the max is 5
這樣做的好處就是:
- 易讀易調試,更容易定位錯誤。
- 可復用,代碼的任何部分都可以在代碼的其他部分中重用。
- 可測試,為代碼的每個功能創建測試更容易。
但是要增加新功能,比如計算中位數,main 函數還是很難維護,因此還需要第二個原則:OCP。
2、開閉原則 OCP
開閉原則(Open Closed Principle)就是對擴展開放,對修改關閉,這可以大大提升代碼的可維護性,也就是說要增加新功能時隻需要添加新的代碼,不修改原有的代碼,這樣做即簡單,也不會影響之前的單元測試,不容易出錯,即使出錯也隻需要檢查新添加的代碼。
上述代碼,可以通過將我們編寫的所有函數變成一個類的子類來解決這個問題。代碼如下:
import numpy as np from abc import ABC, abstractmethod class Operations(ABC): '''Operations''' @abstractmethod def operation(): pass class Mean(Operations): '''Compute Max''' def operation(list_): print(f"The mean is {np.mean(list_)}") class Max(Operations): '''Compute Max''' def operation(list_): print(f"The max is {np.max(list_)}") class Main: '''Main''' def get_operations(list_): # __subclasses__ will found all classes inheriting from Operations for operation in Operations.__subclasses__(): operation.operation(list_) if __name__ == "__main__": Main.get_operations([1,2,3,4,5]) # The mean is 3.0 # The max is 5
如果現在我們想添加一個新的操作,例如:median,我們隻需要添加一個繼承自 Operations 類的 Median 類。新形成的子類將立即被 __subclasses__()
接收,無需對代碼的任何其他部分進行修改。
3、裡氏替換原則 (LSP)
裡式替換原則的英文是 Liskov Substitution Principle,縮寫為 LSP。這個原則最早是在 1986 年由 Barbara Liskov 提出,他是這麼描述這條原則的:
If S is a subtype of T, then objects of type T may be replaced with objects of type S, without breaking the program。
也就是說 子類對象能夠替換程序中父類對象出現的任何地方,並且保證原來程序的邏輯行為不變及正確性不被破壞。
實際上,裡式替換原則還有另外一個更加能落地、更有指導意義的描述,那就是按照協議來設計,子類在設計的時候,要遵守父類的行為約定(或者叫協議)。父類定義瞭函數的行為約定,那子類可以改變函數的內部實現邏輯,但不能改變函數原有的行為約定。這裡的行為約定包括:函數聲明要實現的功能;對輸入、輸出、異常的約定;甚至包括註釋中所羅列的任何特殊說明。
4、接口隔離原則 (ISP)
接口隔離原則的英文翻譯是 Interface Segregation Principle,縮寫為 ISP。Robert Martin 在 SOLID 原則中是這樣定義它的:Clients should not be forced to depend upon interfaces that they do not use。
直譯成中文的話就是:客戶端不應該被強迫依賴它不需要的接口。其中的 客戶端 ,可以理解為接口的調用者或者使用者。
舉個例子:
from abc import ABC, abstractmethod class Mammals(ABC): @abstractmethod def swim(self) -> bool: pass @abstractmethod def walk(self) -> bool: pass class Human(Mammals): def swim(self)-> bool: print("Humans can swim") return True def walk(self)-> bool: print("Humans can walk") return True class Whale(Mammals): def walk(self) -> bool: print("Whales can't walk") return False def swim(self): print("Whales can swim") return True human = Human() human.swim() human.walk() whale = Whale() whale.swim() whale.walk()
執行結果:
Humans can swim
Humans can walk
Whales can swim
Whales can’t walk
事實上,子類鯨魚不應該依賴它不需要的接口 walk,針對這種情況,就需要對接口進行拆分,代碼如下:
from abc import ABC, abstractmethod class Swimer(ABC): @abstractmethod def swim(self) -> bool: pass class Walker(ABC): @abstractmethod def walk(self) -> bool: pass class Human(Swimer,Walker): def swim(self)-> bool: print("Humans can swim") return True def walk(self)-> bool: print("Humans can walk") return True class Whale(Swimer): def swim(self): print("Whales can swim") return True human = Human() human.swim() human.walk() whale = Whale() whale.swim()
5、依賴反轉原則 (DIP)
依賴反轉原則的英文翻譯是 Dependency Inversion Principle,縮寫為 DIP。英文描述:High-level modules shouldn’t depend on low-level modules. Both modules should depend on abstractions. In addition, abstractions shouldn’t depend on details. Details depend on abstractions。
我們將它翻譯成中文,大概意思就是:高層模塊不要依賴低層模塊。高層模塊和低層模塊應該通過抽象(abstractions)來互相依賴。除此之外,抽象不要依賴具體實現細節,具體實現細節依賴抽象。
在調用鏈上,調用者屬於高層,被調用者屬於低層,我們寫的代碼都屬於低層,由框架來調用。在平時的業務代碼開發中,高層模塊依賴低層模塊是沒有任何問題的,但是在框架層面設計的時候,就要考慮通用性,高層應該依賴抽象的接口,低層應該實現對應的接口。如下圖所示:
也就是說本來 ObjectA 依賴 ObjectB,但為瞭擴展後面可能會有 ObjectC,ObjectD,經常變化,因此為瞭頻繁改動,讓高層模塊依賴抽象的接口 interface,然後讓 ObjectB 也反過來依賴 interface,這就是依賴反轉原則。
舉個例子,wsgi 協議就是一種抽象接口,高層模塊有 uWSGI,gunicorn等,低層模塊有 Django,Flask 等,uWSGI,gunicorn 並不直接依賴 Django,Flask,而是通過 wsgi 協議進行互相依賴。
依賴倒置原則概念是高層次模塊不依賴於低層次模塊。看似在要求高層次模塊,實際上是在規范低層次模塊的設計。低層次模塊提供的接口要足夠的抽象、通用,在設計時需要考慮高層次模塊的使用種類和場景。明明是高層次模塊要使用低層次模塊,對低層次模塊有依賴性。現在反而低層次模塊需要根據高層次模塊來設計,出現瞭「倒置」的顯現。
這樣設計好處有兩點:
- 低層次模塊更加通用,適用性更廣
- 高層次模塊沒有依賴低層次模塊的具體實現,方便低層次模塊的替換
最後的話
我去年(2020)年 2 月 3 號購買的《設計模式之美》專欄,將近一年半才把它學習完,再回看之前的代碼,真是一堆垃圾。之前一天寫完的代碼,重構差不多花瞭一個星期,重構之後,感覺還可以再重構的更好,似乎無止境,正如小爭哥說的那樣,項目無論大小,都可以有技術含量,所謂代碼細節是魔鬼,細節到處都存在在取舍,應該怎麼樣,不應該怎麼樣,都大有學問。
以上就是Python編程應用設計原則詳解的詳細內容,更多關於Python編程應用設計原則的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- 淺談python中的多態
- python 多態 協議 鴨子類型詳解
- 示例解析java設計模式七大原則接口隔離原則及優化
- python工廠方法模式原理與實現
- Python 使用 consul 做服務發現示例詳解