“開一閉” 原則(OCP)
經(jīng)典力學(xué)的基石是牛頓三大定律。 而面向?qū)ο蟮目蓮?fù)用設(shè)計(jì) (Object Oriented Design, 或 OOD) 的第一塊基石,便是所謂的”開-閉“原則 (Open-Closed Principle, 酬侵瑁縮寫為OCP)猬仁。
“開-閉 ” 原則講的是:一個(gè)軟件實(shí)體應(yīng)當(dāng)對(duì)擴(kuò)展開放师痕, 對(duì)修改關(guān)閉。 這一原則最早由 Bertrand Meyer [MEYER88]提出丑婿, 英文原文是:Software entities should be open for extension, but closed for modification.
這個(gè)原則說的是, 在設(shè)計(jì)一個(gè)模塊的時(shí)候, 應(yīng)當(dāng)使這個(gè)模塊可以在不被修改的前提下被擴(kuò)展羹奉。 換言之秒旋, 應(yīng)當(dāng)可以在不必修改源代碼的情況下改變這個(gè)模塊的行為。
所有的軟件系統(tǒng)都有一個(gè)共同的性質(zhì)诀拭, 即對(duì)它們的需求都會(huì)隨時(shí)間的推移而發(fā)生變化迁筛。 在軟件系統(tǒng)面臨新的需求時(shí), 系統(tǒng)的設(shè)計(jì)必須是穩(wěn)定的耕挨。 滿足 “開-閉” 原則的設(shè)計(jì)可以給一個(gè)軟件系統(tǒng)兩個(gè)無可比擬的優(yōu)越性:
- 通過擴(kuò)展已有的軟件系統(tǒng)细卧, 可以提供新的行為, 以滿足對(duì)軟件的新需求筒占, 使變化中的軟件系統(tǒng)有一定的適應(yīng)性和靈活性贪庙。
- 已有的軟件模塊,特別是最重要的抽象層模塊不能再修改翰苫, 這就使變化中的軟件系統(tǒng)有一定的穩(wěn)定性和延續(xù)性止邮。
具有這兩個(gè)優(yōu)點(diǎn)的軟件系統(tǒng)是一個(gè)在高層次上實(shí)現(xiàn)了復(fù)用的系統(tǒng), 也是一個(gè)易于維護(hù) 的系統(tǒng)奏窑。
里氏代換原則(LSP)
從“開-閉 ” 原則中可以看出面向?qū)ο笤O(shè)計(jì)的重要原則是創(chuàng)建抽象化导披,并且從抽象化導(dǎo)出具體化。 具體化可以給出不同的版本良哲, 每個(gè)版本都給出不同的實(shí)現(xiàn)盛卡。
從抽象化到具體化的導(dǎo)出要使用繼承關(guān)系和這里要引入的里氏代換原則 (Liskov Substitution Principle, 常縮寫為 LSP) 筑凫。 里氏代換原則由 Barbara Liskov 提出滑沧。
里氏代換原則的嚴(yán)格表達(dá)是:
如果對(duì)每一個(gè)類型為T1的對(duì)象o1,都有類型為T2的對(duì)象o2,使得以Tl定義的所有程序P在所有的對(duì)象o1都代換成o2時(shí),程序P的行為沒有變化巍实,那么類型T2是類型T1的子類型滓技。
換言之,一個(gè)軟件實(shí)體如果使用的是一個(gè)基類的話棚潦,那么一定適用于其子類令漂,而且它根本不能察覺出基類對(duì)象和子類對(duì)象的區(qū)別。
比如丸边,假設(shè)有兩個(gè)類叠必,一個(gè)是Base類,另一個(gè)是Derived類妹窖,而且Derived類是Base類的子類纬朝,那么一個(gè)方法如果可以接受一個(gè)基類對(duì)象b的話:
method1(Base b)
那么它必然可以接受一個(gè)子類對(duì)象d,也即可以有methodl(d)。
里氏代換原則是繼承復(fù)用的基石骄呼。只有當(dāng)衍生類可以替換掉基類共苛,軟件單位的功能不會(huì)受到影響時(shí)判没,基類才能真正被復(fù)用,而衍生類也才能夠在基類的基礎(chǔ)上增加新的行為隅茎。
反過來的代換不成立澄峰。
依賴倒轉(zhuǎn)原則(DIP)
實(shí)現(xiàn) “ 開-閉 ” 原則的關(guān)鍵是抽象化, 并且從抽象化導(dǎo)出具體化實(shí)現(xiàn)辟犀。 如果說 “ 開-閉 ” 原則是面向?qū)ο笤O(shè)計(jì)的目標(biāo)的話俏竞, 依賴倒轉(zhuǎn)原則就是這個(gè)面向?qū)ο笤O(shè)計(jì)的主要機(jī)制[MARTIN00] 。
依賴倒轉(zhuǎn)原則講的是: 要依賴于抽象踪蹬, 不要依賴于具體.
簡(jiǎn)單地說胞此, 依賴倒轉(zhuǎn)原則 (Dependence Inversion Principle) 要求客戶端依賴于抽象耦合。 依賴倒轉(zhuǎn)原則的表述是:
抽象不應(yīng)當(dāng)依賴于細(xì)節(jié)跃捣,細(xì)節(jié)應(yīng)當(dāng)依賴于抽象漱牵。
(Abstractions should not depend upon details. Details should depend upon abstractions)
依賴倒轉(zhuǎn)原則的另一種表述是:
要針對(duì)接口編程, 不要針對(duì)實(shí)現(xiàn)編程疚漆。
(Program to an interface, not an implement-ation)
第二種表述是[GOF95]一書所強(qiáng)調(diào)的酣胀。
針對(duì)接口編程的意思就是說, 應(yīng)當(dāng)使用 Java 接口和抽象 Java 類進(jìn)行變量的類型聲明娶聘、參量的類型聲明闻镶、 方法的返還類型聲明, 以及數(shù)據(jù)類型的轉(zhuǎn)換等丸升。
不要針對(duì)實(shí)現(xiàn)編程的意思就是說铆农, 不應(yīng)當(dāng)使用具體 Java 類進(jìn)行變量的類型聲明、 參 量的類型聲明狡耻、 方法的返還類型聲明墩剖, 以及數(shù)據(jù)類型的轉(zhuǎn)換等。
要保證這一點(diǎn)夷狰,一個(gè)具體Java類應(yīng)當(dāng)只實(shí)現(xiàn)Java接口和抽象Java類中聲明過的方法岭皂,而不應(yīng)當(dāng)給出多余的方法。
倒轉(zhuǎn)依賴關(guān)系強(qiáng)調(diào)一個(gè)系統(tǒng)內(nèi)的實(shí)體之間關(guān)系的靈活性沼头∫妫基本上,如果設(shè)計(jì)師希望遵循”開-閉“原則进倍,那么倒轉(zhuǎn)依賴原則便是達(dá)到要求的途徑土至。
接口隔離原則(ISP)
接口隔離原則 (Interface Segregation Principle, 常常略寫做 ISP) 講的是: 使用多個(gè)專門的接口比使用單一的總接口要好。
換言之猾昆, 從一個(gè)客戶類的角度來講: 一個(gè)類對(duì)另外一個(gè)類的依賴性應(yīng)當(dāng)是建立在最小的接口上的毙籽。
人們所說的 “接口” 往往是指兩種不同的東西: 一種是指 Java 語言中的有嚴(yán)格定義的 Interface 結(jié)構(gòu), 比如java.lang.Runnable 就是一個(gè) Java 接口毡庆;另一種就是一個(gè)類型所具有的方法特征的集合坑赡,也稱做 “接口”,但僅是一種邏輯上的抽象么抗。
應(yīng)于這兩種不同的用詞毅否, 接口隔離原則的表達(dá)方式以及含義都有所不同。
過于臃腫的接口是對(duì)接口的污染(InterfaceContamination)蝇刀。
與迪米特法則的關(guān)系
迪米特法則要求任何一個(gè)軟件實(shí)體螟加,除非絕對(duì)需要,不然不要與外界通信吞琐。即使必須進(jìn)行通信捆探,也應(yīng)當(dāng)盡量限制通信的廣度和深度。
顯然站粟,定制服務(wù)原則拒絕向客戶端提供不需要提供的行為黍图,是符合迪米特法則的。
合成/聚合復(fù)用原則 (CARP)
合成/聚合復(fù)用原則 (Composite/Aggregate Reuse Principle, 或 CARP) 經(jīng)常又叫做合成復(fù)用原則 (Composite Reuse Principle 或 CRP) 奴烙。 合成/聚合復(fù)用原則就是在一個(gè)新的對(duì)象里面使用一些已有的對(duì)象助被, 使之成為新對(duì)象的部分;新的對(duì)象通過向這些對(duì)象的委派達(dá)到復(fù)用已有功能的目的切诀。
這個(gè)設(shè)計(jì)原則有另一個(gè)更簡(jiǎn)短的表述: 要盡量使用合成/聚合揩环, 盡量不要使用繼承。
合成 (Composite) 一詞的使用很廣泛幅虑, 經(jīng)常導(dǎo)致混淆丰滑。 為避免這些混淆, 不妨先來考察一下 “ 合成” 與 “聚合” 的區(qū)別倒庵。
合成和聚合的區(qū)別
合成 (Composition) 和聚合 (Aggregation) 均是關(guān)聯(lián) (Association) 的特殊種類褒墨。 聚合用來表示 “擁有” 關(guān)系或者整體與部分的關(guān)系;而合成則用來表示一種強(qiáng)得多的 “擁有” 關(guān)系哄芜。 在一個(gè)合成關(guān)系里貌亭, 部分和整體的生命周期是一樣的。 一個(gè)合成的新的對(duì)象完全擁有對(duì)其組成部分的支配權(quán)认臊, 包括它們的創(chuàng)建和湮滅等圃庭。 使用程序語言的術(shù)語來講, 組合而成的新對(duì)象對(duì)組成部分的內(nèi)存分配失晴、 內(nèi)存釋放有絕對(duì)的責(zé)任剧腻。
更進(jìn)一步來講, 一個(gè)合成的多重性 (Multiplicity) 不能超過1, 換言之涂屁, —個(gè)合成關(guān)系中的成分對(duì)象是不能與另一個(gè)合成關(guān)系共享的书在。 一個(gè)成分對(duì)象在同一個(gè)時(shí)間內(nèi)只能屬于一個(gè)合成關(guān)系。 如果一個(gè)合成關(guān)系湮滅了拆又, 那么所有的成分對(duì)象要么自己湮滅所有的成分對(duì)象(這種情況較為普遍)儒旬, 要么就得將這責(zé)任交給別人(這種情況較為罕見)栏账。
用 C 程序員較易理解的語言來講, 合成是值的聚合 (Aggregation by Value), 而通常所說的聚合則是引用的聚合 (Aggregation by Reference) 栈源。
迪米特法則(LoD)
迪米特法則 (Law of Demeter 或簡(jiǎn)寫為 LoD) 又叫做最少知識(shí)原則 (Least Knowledge Principle 或簡(jiǎn)寫為 LKP), 就是說挡爵,一個(gè)對(duì)象應(yīng)當(dāng)對(duì)其他對(duì)象有盡可能少的了解。
迪米特法則最初是用來作為面向?qū)ο蟮南到y(tǒng)設(shè)計(jì)風(fēng)格的一種法則甚垦, 于 1987 年秋天由Ian Holland 在美國東北大學(xué) (Northeastern University) 為一個(gè)叫做迪米特 (Demeter) 的項(xiàng)目設(shè)計(jì)提出的茶鹃, 因此叫做迪米特法則[LIEB89] [LIEB86] 。 這條法則實(shí)際上是很多著名系統(tǒng)艰亮, 比如火星登錄軟件系統(tǒng)闭翩、 木星的歐羅巴衛(wèi)星軌道飛船的軟件系統(tǒng)的指導(dǎo)設(shè)計(jì)原則。
迪米特法則的各種表述
沒有任何一個(gè)其他的OO設(shè)計(jì)原則像迪米特法則這樣有如此之多的表述方式迄埃, 下面給出的也只是眾多的表述中較有代表性的幾種:
- 只與你直接的朋友們通信 (Only talk to your immediate friends)疗韵。
- 不要跟 “ 陌生人” 說話 (Don't talk to strangers)。
- 每一個(gè)軟件單位對(duì)其他的單位都只有最少的知識(shí)调俘, 而且局限于那些與本單位密切相關(guān)的軟件單位伶棒。
在上面的表述里面, 什么是 “ 直接 ”彩库、”陌生” 和 “ 密切 ” 則被有意地模糊化了肤无, 以便在不同的環(huán)境下可以有不同的解釋。
狹義的迪米特法則
如果兩個(gè)類不必彼此直接通信骇钦,那么這兩個(gè)類就不應(yīng)當(dāng)發(fā)生直接的相互作用宛渐。 如果其中的一個(gè)類需要調(diào)用另一個(gè)類的某一個(gè)方法的話,可以通過第三者轉(zhuǎn)發(fā)這個(gè)調(diào)用眯搭。
狹義的迪米特法則的缺點(diǎn)
遵循狹義的迪米特法則會(huì)產(chǎn)生一個(gè)明顯的缺點(diǎn):會(huì)在系統(tǒng)里造出大量的小方法窥翩,散落在系統(tǒng)的各個(gè)角落。這些方法僅僅是傳遞間接的調(diào)用鳞仙,因此與系統(tǒng)的商務(wù)邏輯無關(guān)寇蚊。當(dāng)設(shè)計(jì)師試圖從一張類圖看出總體的架構(gòu)時(shí),這些小的方法會(huì)造成迷惑和困擾棍好。
為了克服狹義的迪米特法則的缺點(diǎn)仗岸,可以使用依賴倒轉(zhuǎn)原則,引入一個(gè)抽象的類型引用“抽象陌生人”對(duì)象借笙,使“某人”依賴于“抽象陌生人”扒怖。換言之,就是將“抽象陌生人”變成朋友业稼。
參考資料
《Java與模式》
個(gè)人介紹:
高廣超:多年一線互聯(lián)網(wǎng)研發(fā)與架構(gòu)設(shè)計(jì)經(jīng)驗(yàn)盗痒,擅長設(shè)計(jì)與落地高可用、高性能低散、可擴(kuò)展的互聯(lián)網(wǎng)架構(gòu)俯邓。
本文首發(fā)在 高廣超的簡(jiǎn)書博客 轉(zhuǎn)載請(qǐng)注明骡楼!