在21世紀的前幾年里,“Uncle Bob”Robert Martin引入了用OOP開發軟件的五條原
則,其目的是設計出更易于維護的高質量系統。無論是設計新應用程序,還是重構現有基
本代碼,這些SOLID原則都成為開發人員的地圖。
一 單一職責原則
單一職責原則(Single Responsibility Principle,SRP)指出,每個方法或類應當有且僅有
一個改變的理由。這意味著每個方法或類應當做一件事情,或者只有一項職責。在所有的
SOLID原則中,這是大多數開發人員感到最能完全理解的一條。嚴格來說,這也可能是違
反最頻繁的一條原則了。
二 開放閉合原則
開放/封閉原則(Open/Close Principle,OCP)是指軟件(方法、類等)應當開放擴充且關閉
修改。如果覺得它非常類似于繼承的OOP 原則,那就對了。它們之間的關系非常密切。事
實上,在.NET中OCP就是依賴于繼承的。
OCP的要點在于:作為開發人員,別人偶爾會向我們提供基類,偶爾也會為其他開發人
員生成基類框架,供其使用。這些使用者應當僅能使用這些基類,但不能對其進行修改。
這一點是必要的,因為其他使用者也可能依賴于由基類提供的功能。如果允許使用者修改
這些基類,可能會導致連鎖反應,不僅會影響到應用程序中的各方面,還會影響到企業內
的應用程序。還有一個問題,使用者有時可能會收到基類的升級版本。使用者在升級之前,
必須找出一種方法用來處理其對該基類先前版本中所做的自定義。
于是,問題變為:“那么,如果我需要修改這個基類的工作方式,那應當怎么做呢?”
OCP的另一部分中給出這一答案;基類應當開放,可進行擴充。在這里,擴充是指創建一
個由此基類繼承而來的派生類,它可以擴充或重載基類功能,以提供使用者所需要的特定
功能。這樣,使用者就能使用類的修改版本,而不會影響到類的其他使用者。使用者還可
以在將來更輕松地使用基類的升級版本,因為他們不用擔心丟失自己的修改內容。
三 里氏代換原則
繼承對于OCP,就相當于多態性對于里氏替換原則(Liskov Substitution Principle,LSP)。
LSP 規定:用超類代替應用程序中使用的對象時,應當不會破壞應用程序。這通常也被稱
為“契約式設計(design by contract)”。
回想前面的多態性示例,ComputePay 方法使用了Employee 類型的列表,其中Employee
就是基類型(超類型)。Salary、Hourly 和Seasonal 類都是從Employee 繼承而來,因此它們
是Employee 的子類型。
根據LSP,即使已經將列表聲明為Employee 的列表,也仍然可以用Salary、Hourly
和Seasonal 的具體實例來填充它。因為有了繼承,它們都支持Employee 聲明的相同契約(公
共的方法集或API)。應用程序可以對該列表進行迭代,并調用那些在列表中各個項目的
Employee 上定義的方法,不需要知道或特別關心它們都是什么類型。如果它們支持契約,
該調用就是合法的。
四 接口分離原則
到目前為止,已經在示例中使用了基于類的繼承,但還沒有過多地討論接口。回想一
下,接口就是在代碼中定義的契約,而類同意實現這一契約。這份協議要求類來為接口中
定義的所有方法提供實現。至于如何實現方法,則由這個類來決定,只要它遵守契約,支
持接口中的定義即可。接口是.NET中功能非常強大的功能;它們對繼承和多態的支持方式
與類相同。
接口分離原則(Interface Segregation Principle,ISP)規定,不應當強制客戶端依賴于其
不使用的接口。例如,銀行系統可能有一個用于評估信用申請的服務。為便于討論,假定
該服務不僅處理有質押信用(車船貸款、抵押),也處理無質押信用(信用卡、信用證、股票
信用額度)。如果正在開發一個客戶端,用于幫助從事汽車代理的金融專員為其客戶獲得汽
車貸款,則只需要關注汽車貸款的申請即可,無需考慮有關這一服務的任何其他事情。如
果沒有ISP,應用程序可能必須了解其他方法。
盡管乍看起來這并沒有什么,但它至少是增加了應用程序的復雜性,因為據以進行開
發的API中會有許多方法,遠遠超出所需要的。這樣可能會導致混淆,調用錯誤的方法還
可能會導致潛在的錯誤。還有一種可能,API中未被應用程序用到的部分可能會改變,而
這又會導致對終端的改變。這樣,因為沒用到、沒想用、甚至是根本就不關心的一些功能,
而增加了應用程序的維護成本。這種情況還存在安全風險。該應用程序是專用于汽車貸款
的。如果不道德的開發人員利用這個過于龐大的API來允許利用這一申請擔保其他類型的
信用,又該怎么辦呢?這種問題的嚴重性就不僅僅是代碼癱瘓、不可維護那么簡單了。
這一問題的解決方案就是專門針對客戶端的需要,為該服務創建幾個更小的、更精細
的接口。對于該示例應用程序,專門設計一個針對汽車貸款的接口是比較適當的做法。應
用程序可以用同一實現訪問同一個類,但這一次它使用了一個特定的接口,其中僅有實際
服務的一部分方法。這樣就降低了復雜性,將應用程序與API其他部分的修改隔離開來,
還有助于堵塞安全漏洞。
五 依賴倒轉原則
在完美世界里,應用程序的組件之間沒有耦合關系或綁定關系。開發人員也能夠改變
自己希望改變的任何東西,而不需要擔心在應用程序的其他地方出現缺陷,或者“不希望
存在的負面影響”。令人悲傷的是,我們并不是生活在完美世界里。因此,組件需要相互
綁定在一起,或者在某一點耦合,以構成實際應用程序。
依賴倒置原則(Dependency Inversion Principle,DIP)規定:代碼應當取決于抽象概念,
而不是具體實現;這些抽象不應當依賴于細節;而細節應當依賴于抽象。類可能依賴于其
他類來執行其工作(Employee 服務可能依賴于數據訪問組件向數據存儲中保存和檢索員工
信息)。但是,它們不應當依賴于該類的特定具體實現,而應當是它的抽象。也就是說,
Employee 服務不知道(或不關心)正在使用哪個具體的數據訪問組件——只有它的抽象或代
碼契約(或接口)支持那些用于保存和檢索員工所需要的方法。
顯然,這一概念會大大提高系統的靈活性。如果類只關心它們用于支持特定契約而不
是特定類型的組件,就可以快速而輕松地修改這些低級服務的功能,同時最大限度地降低
對系統其余部分的影響。在第6 章,還會看到如何利用這一概念來模擬這些依賴項,以進
行測試。有時,需要向類中提供這一低級服務的具體實現,以便這個類能夠完成自己的工
作。最常見的做法,特別是在.NET中使用TDD 的開發人員,就是依賴項注入(DI)模式。
============以上OOP軟件設計的SOLID原則==============
控制反轉原則
全局注冊樹
ORM 對象數據映射
合成聚合原則
啟動和執行分離原則
面向接口編程
配置加載 不是硬編程原則