SOLID原則、設計模式適用於Python語言嗎
在閱讀 clean architecture的過程中,會發現作者經常提到recompile
redeploy
,這些術語看起來都跟靜態型別語言有關,比如Java、C++、C#。而在我經常使用的python語言中,是不存在這些概念的。於是,在閱讀的時候就會有一個疑惑,《clean architecture》中提到的各種原則,比如SOLID,是否對動態型別語言 -- 如python -- 同樣適用?
SOLID是面向物件設計的指導原則,更具適用性的應該是各種設計模式,GOF經典的Design Patterns: Elements of Reusable Object-Oriented Software 也是用C++來舉例的,那麼這些經典設計模式有多少是適用於動態語言如python的呢?本文記錄對這些問題淺薄的思考,如果有認知錯誤的地方,還請大家不吝指教。
本文地址:https://www.cnblogs.com/xybaby/p/11782293.html
SOLID
SOLID是模組(module)設計的指導原則,有以下五個原則組成
- SRP(Single responsibility principle):單一職責原則,一個module只有一個原因修改
- OCP(Open/closed principle):開放-關閉原則,開放擴充套件,關閉修改
- LSP(Liskov substitution principle):里氏替換原則,子型別必須能夠替換它們的基型別
- ISP(Interface segregation principle):介面隔離原則,你所依賴的必須是真正使用到的
- DIP(Dependency inversion principle):依賴倒置原則,依賴介面而不是實現(高層不需要知道底層的實現)
ISP
首先來看ISP,介面隔離原則,《clean architecture》的作者承認這是一個語言相關的原則
This fact could lead you to conclude that the ISP is a language issue, rather than an architecture issue.
為什麼呢, ISP主要是為了解決“胖介面”導致的不必要的 recompilation and redeployment, 如下所示:
Use1對op1
的使用導致OPS的修改,導致User2 User3也要重新編譯。而在動態語言中是不存在重新編譯這樣的問題的:
In dynamically typed languages like Ruby and Python, such declarations don’t exist in source code. Instead, they are inferred at runtime. Thus there are no source code dependencies to force recompilation and redeployment
DIP
DIP(依賴倒置原則)是SOLID的核心,OCP其實就依賴於DIP。也可以說,DIP是“clean architecture”的核心。
“clean architecture”由兩部分組成:
- well-isolated components
- dependency rule
什麼是”Dependency rule"呢?讓低層的detail去依賴高層的policy。比如,業務邏輯(business rule)就相比資料儲存(database)出於更高層,雖然邏輯上是業務邏輯要使用資料庫,但為了可維護性、可擴充套件性,架構設計上得讓database去依賴business rule,如下所示
從上圖可以看出,為了達到這個目的,在靜態語言中,會宣告一個介面,呼叫的雙方都依賴這個介面。如上圖中的database interface
,讓business rule和database都去依賴這個介面,而這個database interface和business rule在一個component,這就實現了讓低層的database去依賴高層的business rule。
在靜態型別語言(如Java、C++)中,其實就是利用執行時多型這個特性,使得可以在執行時 -- 而不是編譯時 -- 改變軟體的行為,當然為了達到這個目的,需要預先宣告一個虛基類 或者介面(Java Interface)。
而在python中,本來就是執行的時候去求值,而且因為ducking type,所以無需事先宣告介面或者強迫繼承介面
Dependency structures in these languages(dynamic typed languages) are much simpler because dependency inversion does not require either the declaration or the inheritance of interfaces.
從靜態型別語言到動態型別語言,其實是省略了很多東西
- 省略了虛擬函式,如template method模式
- 省略了虛基類、介面,如DIP、strategy模式
python中的依賴與依賴倒置
在python中,怎麼算依賴,怎麼算依賴倒置?
'''my.py'''
import other
class My(object):
def f(self):
other.act()
這一段程式碼中通過import
讓module my
依賴於module other
,
'''my.py'''
class My(object):
def __init__(self, actor):
self._actor = actor
def f(self):
self._actor.act()
那麼在這裡,my和other有依賴關係嗎?沒有的,這裡壓根就沒有出現過other。由於動態型別加上ducking type,根本無需顯式的介面定義,只要遵循相關的協議(契約)即可。而這個契約,沒辦法通過程式碼強行約束,呼叫者需要什麼樣的介面,被呼叫者應該具備什麼樣的行為,都只能通過文件(或者單元測試)來描述。
為了表達契約,上述程式碼應該加上docstring
'''my.py'''
class My(object):
def __init__(self, actor):
'''Param: actor,該物件需要具備接收0個引數的act方法
'''
self._actor = actor
def f(self):
self._actor.act()
python中大量使用類似的協議,如context management
, iterator protocol
。雖然很方便,同時也對程式設計師有更高要求,因為至少得有靠譜的docstring。如果需要強加約束,那是是可以考慮使用abc的。
設計模式
首先宣告的是,在本文中提到的設計模式,一般指Design Patterns: Elements of Reusable Object-Oriented Software 這本書中所描述的經典設計模式。
很早之前看過一種說法,“++設計模式是對靜態語言缺陷的彌補”++,當時沒經思考就全盤接受了,竊認為這就是真理。最近才真正思考這個問題,發現這種說法存在偏見與不全面。
首先丟擲一個問題:設計模式是語言相關嗎(language-specific)?是某種型別的程式語言需要設計模式,而另外一些程式語言就不需要?或者說,不同的程式語言需要的設計模式是不一樣的?
什麼是設計模式呢,《Design Patterns》中描述為,針對軟體設計中某一類特定問題的簡單且優美的解決方案。
Describes simple and elegant solutions to specific problems in object-oriented software design
也就是說,設計模式是解決某類特定問題的套路,或者說方法論。套路是針對某個問題,經過理論或實踐驗證的、行之有效的方法與步驟。沒有方法論也能解決問題,可能就需要去大量的嘗試、試錯,得到一種解決辦法(大概率也不是最優解),這個求解的過程耗時且低效。因此可以說,方法論(模式)加速了問題求解的過程。
比如,程式設計師每天都很大量的事情要做:要開會、要寫程式碼、要處理bug、要自己充電。如何安排呢?可能自己思考這個問題就得焦頭爛額,但是已經有成熟的方法論 --艾森豪威爾矩陣-- 可供使用了啊。
我們常說,站在巨人的肩膀上,套路、方法論就是巨人的肩膀。
設計模式同樣如此。
設計模式與動態語言
《Design Patterns》這本書,寫於1994年,作者提到寫這本數的目標,就是將這些行之有效的經驗記錄下來。前面提到,設計模式是針對一類問題的解決方案,那麼在介紹一種設計模式的時候,就一定會涉及到以下內容(包括但不限於):
- 要解決的問題是什麼
- 解決方案是什麼樣子的
- 解決方案的缺陷與適用場景
- 解決方案的詳細步驟
- 針對同一個問題,有沒有其他解決方案,各自的優劣
當然,首先得給這個模式取一個恰如其分的名字,命名的重要性不容質疑。至少保證程式設計師之間在溝通的時候所表達的是同一個問題,不管這個溝通是peer to peer,還是通過程式碼。名字(術語、定義)也就減輕了溝通的成本。
在《Design Patterns》寫成的兩年後,即1996年,Peter Norvig就做了一個分享 “Design Patterns in Dynamic Programming”, 指出由於動態語言存在更少的語言層面的限制,GOF中的大多數設計模式在Lisp或者Dylan有更簡單的實現,有的甚至簡單到根本無需注意
16 of 23 patterns have qualitatively simpler implementation in Lisp or Dylan than in C++ for at least some uses of each pattern
16 of 23 patterns are either invisible or simpler
那麼哪些模式變得“invisible”,哪些是“simpler”了呢?
《Design Patterns》中講設計模式大致分為三類
- Creational: ways and means of object instantiation
- Structural: mutual composition of classes or objects (the Facade DP is Structural)
- Behavioral: how classes or objects interactand distribute responsibilities among them
由於在動態型別語言中,類(class, type)和方法(function)都是一等公民,因此Creational patterns
在動態型別語言,如Python中就變得“invisible”。
由於動態型別、ducking type,一些Creational patterns
如“Observer”,“Visitor”就變得“simpler”。這裡要強調的是,變得更簡單,並不意味這個這個模式就沒有存在的意義了,比如觀察者模式,或者訂閱-釋出,代表了鬆耦合的設計原則,在各個層級的設計中都是需要的。
對於這種體現更高原則、思想的設計模式,我們應該用模式去幫助思考和溝通,而不要拘泥於樣板程式碼、特定語言實現。StackExchange上的這個排比句很恰當:
- I might say that I have a visitor pattern, but in any language with first class functions it will be just a function taking a function. Instead of factory class I usually have just a factory function.
- I might say I have an interface, but then it's just a couple of methods marked with comments, because there wouldn't be any other implementation (of course in python an interface is always just comments, because it's duck-typed).
- I still speak of the code as using the pattern, because it's a useful way to think about it, but don't actually type in all the stuff until I really need it.
那麼回到問題,設計模式是語言相關嗎(language-specific)?
我的回答是,部分設計模式是語言相關的,部分設計模式不是語言相關的,具體到某一個特定的模式還可能是變化的。
為什麼呢,嚴謹一點,我們只能說設計模式是問題相關的 -- 是關乎某個問題的。核心在於,這個問題在什麼情況下確實是一個問題。而且,隨著發展,一個老問題會消亡,新問題會出現。
具體到程式語言,則應該關心的是一個問題是不是語言相關的。在靜態型別語言,如C++中,物件都有型別,型別決定了其行為,那麼為了執行時多型,就得有一個虛基類,同時還要做到OCP,這就需要各式各樣的Creational Patterns。但到了動態型別語言,這個就不再是一個問題,因此就不再有與之對應的模式。
references
- clean architecture
- Design Patterns: Elements of Reusable Object-Oriented Software
- Are there any design patterns that are unnecessary in dynamic languages like Python?
- Design Patterns in Dynamic Programming
- 解密“設計模式”
- Design Patterns in python