標籤:
DIP 依賴倒置原則
- 高層模組不應該依賴於低層模組。二者都應該依賴於抽象。
- 抽象不應該依賴於細節。細節應該依賴於抽象。
依賴於低層模組的高層模組意味著什嗎?正是高層模組包含了應用程式中重要的策略選擇和業務模型。這些高層模組使得其所在的應用程式區別於其他。然而,如果這些高層模組依賴於低層模組,那麼對於低層模組的改動會直接影響到高層模組,從而迫使它們依次做出改動。如果高層模組獨立於低層模組,那麼高層模組就可以非常容易地被重用。該原則是架構設計的核心原則。
層次化
糟糕的層次關係。
更為適合的模型。每個較高層都為它所需要的服務聲明一個抽象介面。較低的層次實現了這些抽象介面。每個高層類都通過該抽象介面使用下一層。這樣高層就不依賴於低層。低層反而依賴與在高層中聲明的抽象服務介面。這不僅解除了PolicyLayer對於UtilityLayer的傳遞依賴關係,甚至也解除了PolicyLayer對於MechanismLayer的依賴關係。
倒置的介面所有權
倒置不僅僅是依賴關係的倒置,它也是介面所有權的倒置。我們通常會認為工具庫應該擁有它們自己的介面。但是當應用了DIP時,我們發現往往是客戶擁有抽象介面,而它們的服務者則從這些抽象介面派生。
這就是著名的Hollywood原則:"Don‘t call us, we‘ll call you.(不要調用我們,我們會調用你。)"低層模組實現了在高層模組中聲明並被高層模組調用的介面。
通過這種倒置的介面所有權,對於MechanismLayer和UtilityLayer的任何改動都不會再影響到PoliyLayer。而且,PolicyLayer可以在定義了符合PolicyServiceInterface的任何上下文中重用。這樣,通過倒置這些依賴關係,我們建立了一個更靈活、更持久、更易改變的結構。
這裡所說的所有權僅僅是指介面是隨擁有它們的客戶程式發布的,而非實現它們的伺服器程式。介面和客戶程式位於同一個包或者庫中。這就迫使伺服器程式庫或者包依賴於客戶程式庫或者包。
當然,有時我們會不想讓伺服器程式依賴於客戶程式,特別是當有多分客戶程式但是伺服器卻僅有一份時。在這種情況下,客戶程式必須得遵循服務介面,並把它發布到一個獨立的包中。
依賴於抽象
程式中所有的依賴關係都應該終止於抽象類別或者介面。
- 任何變數都不應該持有一個指向具體類的引用。
- 任何類都不應該從具體類派生。
- 任何方法都不應該重寫它的任何基類中已經實現了的方法。
當然,每個程式都會有違反該啟發規則的情況。有時必須建立具體類的執行個體,而建立這些執行個體的模組將會依賴於它們。此外,該啟發規則對於那些雖然是具體但卻穩定的來來說似乎不大合理。如果一個具體類不太會改變,並且也不會建立其他類似的衍生類別,那麼依賴於它並不會造成損害。
比如,在大多數系統中,描述字串的類都是具體的。例如,在C#中的String。該類是穩定的。也就是說,它不太會改變。因此,直接依賴於它不會造成傷害。
如果一個不穩定的類的介面必須變化是,這個變化一定會影響到表示該類的抽象介面。這種變化破壞了由抽象介面維繫的隔離性。
由此可知,該啟發規則對問題的考慮有點兒簡單了。另一方面,如果看得更遠一點,認為是客戶模組或者層來聲明它們需要的服務介面,那麼僅當客戶需要時才會對介面進行改變。這樣,改變實現抽象介面的類就不會影響到客戶。
找出潛在的抽象
介面可以被許多不同的客戶使用,並被許多不同的服務者實現。這樣,介面就需要獨立存在而不屬於任何一方。在C#中,可以把它放在一個單獨的命名控制項和庫中。
結論
使用傳統的過程化程式設計所建立出來的依賴關係結構、策略是依賴於細節的。這是糟糕的,因為這樣會使策略受到細節的改變的影響。物件導向的程式設計倒置了依賴關係結構,使得細節和策略都依賴於抽象,並且常常是客戶程式擁有服務介面。
事實上,這種依賴關係倒置正是好的物件導向設計的標誌所在。使用何種語言編程是無關緊要的。如程式的依賴關係是倒置的,它就是物件導向的設計。如果程式的依賴關係不是倒置的,它就是過程化的設計。
依賴倒置原則是實現許多物件導向技術所宣稱的好處的基本低層機制。它的正確應用對於建立可重用的架構來說是必需的。同時它對於構建在變化面前富有彈性的代碼也是非常重要的。由於抽象和細節彼此隔離,所以代碼也非常容易維護。
摘錄自:[美]RobertC.Martin、MicahMartin著,鄧輝、孫鳴譯 敏捷式軟體開發 (Agile Software Development)原則、模式與實踐(C#版修訂版) [M]、人民郵電出版社,2013、115-121、
敏捷式軟體開發 (Agile Software Development) – DIP 依賴倒置原則