在 Python 中,繼承并非類型建模的核心手段。多態來源于調用點,接口產生于使用方式,而對象之間的可替換性取決于行為一致性,而非形式上的繼承關系。因此,在實踐層面,當需要擴展能力、組織復雜行為或應對需求變化時,組合應當優先于繼承。這并非一句設計口號,而是 Python 對象模型、協議機制與動態行為綁定方式自然導出的結論。
10.1 組合的基本思想
組合(Composition) 的思想非常樸素:不要通過“成為某種類型”來獲得能力,而是通過“擁有某種對象”來獲得能力。
(1)繼承方式:通過 is-a 關系獲得能力
car.move() # 通過繼承獲得 move 能力在繼承方案中,Car 通過成為 Vehicle 的子類獲得 move() 行為,其能力來源與類型身份被強綁定。一旦繼承關系成立,Car 便不可避免地承載 Vehicle 的全部語義。
(2)組合方式:通過 has-a 關系獲得能力
car2.start() # Engine started而在組合方案中,Car2 并未聲明自己“是某種類型”,而是通過持有 Engine 對象獲得啟動能力。能力的來源是對象協作關系,而非類型歸屬關系。
這正是組合的核心思想:對象通過“擁有誰”來獲得能力,而不是通過“成為誰”來獲得能力。能力是可配置的,而身份不再被固化。
10.2 行為組合而非類型擴展
繼承的一個根本問題在于:它擴展的是“類型整體”,而非“具體所需行為”。
(1)繼承的問題:無關能力被一并繼承
UserService 只關心數據庫連接能力,卻被迫繼承了 backup() 這一與其職責無關的行為。這并非代碼錯誤,而是類型繼承天然放大的語義耦合。
上述示例揭示了繼承在復雜系統中的一個根本問題:繼承擴展的是“父類的整體語義”,而非子類真正需要的那一部分行為。
(2)使用組合,可以只獲取真正需要的行為
service.get_user(123)組合方案中,UserService2 僅依賴 get_connection() 這一最小行為協議。它并不關心連接來自連接池、直連數據庫,還是其他來源。
這表明,組合關注“行為拼裝”,繼承則綁定“語義整體”。前者更符合 Python 的協議與使用導向設計。
10.3 委托模式的自然表達
在 Python 語境中,“委托”并不是一種需要刻意實現的設計模式,而是一種對象協作關系的自然結果。
所謂委托(Delegation),是指一個對象并不自行完成某項職責,而是將該職責轉交給其持有的另一個對象,并由后者完成具體工作。調用方只與“委托者”交互,而實際行為由“被委托者”承擔。
與繼承不同,委托并不建立類型層級,也不形成身份綁定。它只表達一種運行期的職責分工關系:誰負責協調,誰負責執行。這使得行為可以被自由替換,而對象身份保持穩定。
processor.process("test")DataProcessor 不自己實現難邏輯,而是接收一個 validator 對象并將校驗職責委托給它。只要該對象提供 validate() 行為,就可以參與協作。
關鍵不在于 SimpleValidator 是否繼承自某個基類,而在于它是否履行了被委托的職責。
這種設計使得:
? 職責劃分清晰
? 校驗策略可在運行期替換
? 新行為無需修改既有類型層級
委托在這里不是“應用某種模式”,而是組合思想在 Python 對象模型中的自然體現。
10.4 組合帶來的演化自由
組合最大的價值,往往并不體現在初始設計,而體現在系統的演化階段。
)).run()Application 并不關心日志的具體實現細節,只依賴 log() 行為協議。日志能力既可以來自單一對象,也可以來自多個對象的組合。
當需求從“只打印”演化為“同時打印并寫文件”時,繼承方案往往需要引入新的子類或調整層級,而組合方案只需重新拼裝已有組件。
這正是 Python 倡導的設計方向:穩定的是接口與使用方式,可變的是實現與組合結構。
10.5 組合與測試友好性
組合能夠顯著提升可測試性,其關鍵原因在于它天然支持“依賴注入”。
所謂依賴注入(Dependency Injection),并不是指某種框架或技術,而是一種構造方式上的選擇:對象不自行創建其依賴,而是通過構造參數、屬性或方法參數接收外部提供的依賴對象。
這種方式將“對象的行為邏輯”與“依賴的獲取方式”解耦,使得依賴可以在不同環境下被自由替換,尤其適合測試、模擬與運行期配置。
return {"status": "success", "amount": amount} OrderService 通過初始化函數接收 payment_gateway,使其核心邏輯與外部依賴完全解耦。
? 依賴注入:將對象的依賴通過參數傳遞,而不是在對象內部創建
? 控制反轉:控制權從 OrderService 轉移到外部調用者
? 依賴倒置:依賴抽象(charge 方法接口),不依賴具體實現
測試時,可以輕松注入一個行為可控、狀態可觀察的模擬對象。
print(f" 結果: {flexible_service.place_order(300)}")這種設計避免了:
? 復雜的繼承測試替身
? 隱式的全局依賴
? 對真實外部系統的調用
組合使測試關注點回歸業務行為本身,而不是被迫處理類型結構。這也是 Python 項目中普遍采用依賴注入與組合的重要原因。
10.6 組合與策略模式
當系統中存在可變行為需要在運行期進行切換或替換時,組合比繼承更具穩定性與表達力。所謂“策略模式”(Strategy Pattern),本質上正是組合思想在行為層面的一種具體體現。
在 Python 中,策略并不等同于固定的類層級或抽象接口結構。它強調的是:將可變算法獨立封裝為對象,并通過組合在運行期注入或替換。調用方只依賴穩定的行為協議,而不關心具體策略的類型身份。
因此,策略模式并非組合之外的另一種設計技巧,而是組合在“可變行為”這一問題域中的自然落點。策略對象可以是任意提供所需行為的對象,是否繼承自統一父類并不關鍵。
下面的示例將展示:策略模式在 Python 中往往通過組合與委托自然形成,而非通過復雜的繼承結構刻意實現。
archiver.archive("data") # 輸出:"GZIP compressed: data"策略是否繼承自同一父類并不重要,重要的是調用點依賴的行為是否一致。
DataArchiver 只依賴 compress() 行為協議,因此壓縮策略可以在運行期自由替換。這種替換并不會改變 DataArchiver 的身份或結構。
策略模式的優勢在此體現為:
? 算法可互換:同一操作(archive)使用不同算法(ZIP/GZIP)
? 運行時靈活性:無需重新創建對象即可改變行為
? 客戶端透明:archive() 調用方式不變,但內部算法已改變
很顯然,策略模式并非以繼承為中心,而是以行為協議為中心。
這再次表明:在 Python 中,組合與協議天然協同,而繼承只是可選工具。
小結
在 Python 中,能力的獲得不應依賴類型身份,而應來源于對象協作。組合通過行為拼裝而非類型擴展,使對象關系保持松耦合、可替換且易演化。委托、策略與依賴注入并非刻意設計的模式,而是組合思想在 Python 對象模型中的自然結果。將繼承限制為少數穩定擴展點,并優先采用組合,是 Python 面向對象設計保持靈活性與可維護性的根本原因。
![]()
“點贊有美意,贊賞是鼓勵”
特別聲明:以上內容(如有圖片或視頻亦包括在內)為自媒體平臺“網易號”用戶上傳并發布,本平臺僅提供信息存儲服務。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.