“開一閉” 原則(OCP)
經典力學的基石是牛頓三大定律。 而面向對象的可複用設計 (Object Oriented Design, 或 OOD) 的第一塊基石,便是所謂的”開-閉“原則 (Open-Closed Principle, 常縮寫為OCP)。
“開-閉 ” 原則講的是:一個軟件實體應當對擴展開放, 對修改關閉。 這一原則最早由 Bertrand Meyer [MEYER88]提出, 英文原文是:Software entities should be open for extension, but closed for modification.
這個原則說的是, 在設計一個模塊的時候, 應當使這個模塊可以在不被修改的前提下被擴展。 換言之, 應當可以在不必修改源代碼的情況下改變這個模塊的行為。
所有的軟件系統都有一個共同的性質, 即對它們的需求都會隨時間的推移而發生變化。 在軟件系統面臨新的需求時, 系統的設計必須是穩定的。 滿足 “開-閉” 原則的設計可以給一個軟件系統兩個無可比擬的優越性:
通過擴展已有的軟件系統, 可以提供新的行為, 以滿足對軟件的新需求, 使變化中的軟件系統有一定的適應性和靈活性。
已有的軟件模塊,特別是最重要的抽象層模塊不能再修改, 這就使變化中的軟件系統有一定的穩定性和延續性。
具有這兩個優點的軟件系統是一個在高層次上實現了複用的系統, 也是一個易於維護 的系統。
里氏代換原則(LSP)
從“開-閉 ” 原則中可以看出面向對象設計的重要原則是創建抽象化,並且從抽象化導出具體化。 具體化可以給出不同的版本, 每個版本都給出不同的實現。
從抽象化到具體化的導出要使用繼承關係和這裡要引入的里氏代換原則 (Liskov Substitution Principle, 常縮寫為 LSP) 。 里氏代換原則由 Barbara Liskov 提出。
里氏代換原則的嚴格表達是:
如果對每一個類型為T1的對象o1,都有類型為T2的對象o2,使得以Tl定義的所有程序P在所有的對象o1都代換成o2時,程序P的行為沒有變化,那麼類型T2是類型T1的子類型。
換言之,一個軟件實體如果使用的是一個基類的話,那麼一定適用於其子類,而且它根本不能察覺出基類對象和子類對象的區別。
比如,假設有兩個類,一個是Base類,另一個是Derived類,而且Derived類是Base類的子類,那麼一個方法如果可以接受一個基類對象b的話:
method1(Base b)
那麼它必然可以接受一個子類對象d,也即可以有methodl(d)。
里氏代換原則是繼承複用的基石。只有當衍生類可以替換掉基類,軟件單位的功能不會受到影響時,基類才能真正被複用,而衍生類也才能夠在基類的基礎上增加新的行為。
反過來的代換不成立。
依賴倒轉原則(DIP)
實現 “ 開-閉 ” 原則的關鍵是抽象化, 並且從抽象化導出具體化實現。 如果說 “ 開-閉 ” 原則是面向對象設計的目標的話, 依賴倒轉原則就是這個面向對象設計的主要機制[MARTIN00] 。
依賴倒轉原則講的是: 要依賴於抽象, 不要依賴於具體.
簡單地說, 依賴倒轉原則 (Dependence Inversion Principle) 要求客戶端依賴於抽象耦合。 依賴倒轉原則的表述是:
抽象不應當依賴於細節,細節應當依賴於抽象。
(Abstractions should not depend upon details. Details should depend upon abstractions)
依賴倒轉原則的另一種表述是:
要針對接口編程, 不要針對實現編程。
(Program to an interface, not an implementation)
第二種表述是[GOF95]一書所強調的。
針對接口編程的意思就是說, 應當使用 Java 接口和抽象 Java 類進行變量的類型聲明、參量的類型聲明、 方法的返還類型聲明, 以及數據類型的轉換等。
不要針對實現編程的意思就是說, 不應當使用具體 Java 類進行變量的類型聲明、 參 量的類型聲明、 方法的返還類型聲明, 以及數據類型的轉換等。
要保證這一點,一個具體Java類應當只實現Java接口和抽象Java類中聲明過的方法,而不應當給出多餘的方法。
倒轉依賴關係強調一個系統內的實體之間關係的靈活性。基本上,如果設計師希望遵循”開-閉“原則,那麼倒轉依賴原則便是達到要求的途徑。
接口隔離原則(ISP)
接口隔離原則 (Interface Segregation Principle, 常常略寫做 ISP) 講的是: 使用多個專門的接口比使用單一的總接口要好。
換言之, 從一個客戶類的角度來講: 一個類對另外一個類的依賴性應當是建立在最小的接口上的。
人們所說的 “接口” 往往是指兩種不同的東西: 一種是指 Java 語言中的有嚴格定義的 Interface 結構, 比如java.lang.Runnable 就是一個 Java 接口;另一種就是一個類型所具有的方法特徵的集合,也稱做 “接口”,但僅是一種邏輯上的抽象。
應於這兩種不同的用詞, 接口隔離原則的表達方式以及含義都有所不同。
過於臃腫的接口是對接口的汙染(InterfaceContamination)。
與迪米特法則的關係
迪米特法則要求任何一個軟件實體,除非絕對需要,不然不要與外界通信。即使必須進行通信,也應當儘量限制通信的廣度和深度。
顯然,定製服務原則拒絕向客戶端提供不需要提供的行為,是符合迪米特法則的。
合成/聚合複用原則 (CARP)
合成/聚合複用原則 (Composite/Aggregate Reuse Principle, 或 CARP) 經常又叫做合成複用原則 (Composite Reuse Principle 或 CRP) 。 合成/聚合複用原則就是在一個新的對象裡面使用一些已有的對象, 使之成為新對象的部分;新的對象通過向這些對象的委派達到複用已有功能的目的。
這個設計原則有另一個更簡短的表述: 要儘量使用合成/聚合, 儘量不要使用繼承。
合成 (Composite) 一詞的使用很廣泛, 經常導致混淆。 為避免這些混淆, 不妨先來考察一下 “ 合成” 與 “聚合” 的區別。
合成和聚合的區別
合成 (Composition) 和聚合 (Aggregation) 均是關聯 (Association) 的特殊種類。 聚合用來表示 “擁有” 關係或者整體與部分的關係;而合成則用來表示一種強得多的 “擁有” 關係。 在一個合成關係裡, 部分和整體的生命週期是一樣的。 一個合成的新的對象完全擁有對其組成部分的支配權, 包括它們的創建和湮滅等。 使用程序語言的術語來講, 組合而成的新對象對組成部分的內存分配、 內存釋放有絕對的責任。
更進一步來講, 一個合成的多重性 (Multiplicity) 不能超過1, 換言之, —個合成關係中的成分對象是不能與另一個合成關係共享的。 一個成分對象在同一個時間內只能屬於一個合成關係。 如果一個合成關係湮滅了, 那麼所有的成分對象要麼自己湮滅所有的成分對象(這種情況較為普遍), 要麼就得將這責任交給別人(這種情況較為罕見)。
用 C 程序員較易理解的語言來講, 合成是值的聚合 (Aggregation by Value), 而通常所說的聚合則是引用的聚合 (Aggregation by Reference) 。
迪米特法則(LoD)
迪米特法則 (Law of Demeter 或簡寫為 LoD) 又叫做最少知識原則 (Least Knowledge Principle 或簡寫為 LKP), 就是說,一個對象應當對其他對象有儘可能少的瞭解。
迪米特法則最初是用來作為面向對象的系統設計風格的一種法則, 於 1987 年秋天由Ian Holland 在美國東北大學 (Northeastern University) 為一個叫做迪米特 (Demeter) 的項目設計提出的, 因此叫做迪米特法則[LIEB89] [LIEB86] 。 這條法則實際上是很多著名系統, 比如火星登錄軟件系統、 木星的歐羅巴衛星軌道飛船的軟件系統的指導設計原則。
迪米特法則的各種表述
沒有任何一個其他的OO設計原則像迪米特法則這樣有如此之多的表述方式, 下面給出的也只是眾多的表述中較有代表性的幾種:
只與你直接的朋友們通信 (Only talk to your immediate friends)。
不要跟 “ 陌生人” 說話 (Don't talk to strangers)。
每一個軟件單位對其他的單位都只有最少的知識, 而且侷限於那些與本單位密切相關的軟件單位。
在上面的表述裡面, 什麼是 “ 直接 ”、”陌生” 和 “ 密切 ” 則被有意地模糊化了, 以便在不同的環境下可以有不同的解釋。
狹義的迪米特法則
如果兩個類不必彼此直接通信,那麼這兩個類就不應當發生直接的相互作用。 如果其中的一個類需要調用另一個類的某一個方法的話,可以通過第三者轉發這個調用。
狹義的迪米特法則的缺點
遵循狹義的迪米特法則會產生一個明顯的缺點:會在系統裡造出大量的小方法,散落在系統的各個角落。這些方法僅僅是傳遞間接的調用,因此與系統的商務邏輯無關。當設計師試圖從一張類圖看出總體的架構時,這些小的方法會造成迷惑和困擾。
為了克服狹義的迪米特法則的缺點,可以使用依賴倒轉原則,引入一個抽象的類型引用“抽象陌生人”對象,使“某人”依賴於“抽象陌生人”。換言之,就是將“抽象陌生人”變成朋友。
《Java與模式》
個人介紹:
高廣超:多年一線互聯網研發與架構設計經驗,擅長設計與落地高可用、高性能、可擴展的互聯網架構。
本文首發在 高廣超的簡書博客 轉載請註明!
閱讀更多 互聯網技術棧 的文章