[2]動(dòng)機(jī)、原則與模式——OO設(shè)計(jì)之我見

OO設(shè)計(jì)的三寶
在講具體的原則之前,我想先明確一下面向?qū)ο笳Z言的三個(gè)特性。所有的面向?qū)ο笳Z言都首先必須支持這三個(gè)特性,才能稱之為OO語言。也只有這三個(gè)特性的支持,才有了后面的各種原則和模式之說。所以這是OO設(shè)計(jì)的“三寶”。

首先是封裝,封裝的本質(zhì)的是將行為寓于數(shù)據(jù)之中。注意到這是面向?qū)ο笳Z言與面向過程語言最大的區(qū)別,不用贅述。其次是繼承,繼承對(duì)于老的教學(xué)方式,總是強(qiáng)調(diào)拓展屬性,即具體到更具體。這是欠妥當(dāng)?shù)?。相?yīng)的,我們應(yīng)該更多的認(rèn)為繼承是一種橋梁,把抽象和具體連接起來。

多態(tài)是三寶中最重要的一個(gè)。封裝和繼承都是為了多態(tài)做鋪墊。正因?yàn)橛辛硕鄳B(tài),才有了面向?qū)ο笤O(shè)計(jì)的那些原則和模式,才有可能產(chǎn)生高內(nèi)聚低耦合的軟件系統(tǒng)。所以說,對(duì)于軟件開發(fā),多態(tài)就像是普羅米修斯帶給人類的圣火。這種評(píng)價(jià)是毫不夸張的,越懂OO的設(shè)計(jì),越能理解多態(tài)的重要性。Java成為OO語言的翹楚,我個(gè)人認(rèn)為與其天然的支持多態(tài)是有一定關(guān)系的。

我用一個(gè)形象的例子總結(jié)一下OO語言的這三個(gè)特性。假設(shè)我們有一個(gè)異質(zhì)鏈表,類型為OfficeTool,這個(gè)抽象類對(duì)象代表一種Office工具。它會(huì)有很多的方法,例如有一個(gè)方法叫g(shù)etYourBestOutput,意即“返回自己最好的輸出”。(方法寓于對(duì)象之中,這就是封裝。)這個(gè)鏈表中有不同的對(duì)象,它們都是OfficeTool這種對(duì)象的子類,其中三個(gè)就是Word,Excel和PowerPoint。(子對(duì)象擁有父對(duì)象的方法,可以以父對(duì)象的名字進(jìn)行引用,這就是繼承。)如果遍歷這個(gè)異質(zhì)鏈表,訪問剛才提到的方法時(shí),我們知道,這三個(gè)工具各有所長,所以Word會(huì)輸出一部精心排版的書稿,Excel會(huì)輸出一份內(nèi)容詳實(shí)的財(cái)報(bào),而PowerPoint會(huì)輸出一個(gè)制作精美的演示文稿。(不同子類的相同方法,表現(xiàn)出不同的結(jié)果或輸出,這就是多態(tài)?。?/p>

迪米特法則
迪米特法則有多種表述:

Each unit should have only limited knowledge about other units: only units "closely" related to the current unit.(每一個(gè)軟件單位對(duì)其他的單位都只有最少的知識(shí),而且局限于那些與本單位密切相關(guān)的軟件單位。)
Each unit should only talk to its friends; don't talk to strangers.(每個(gè)軟件單位都只與自己的朋友對(duì)話,不要和陌生人說話。)
Only talk to your immediate friends.(只與你最親近的朋友通信。)

作為法律,法則,它強(qiáng)調(diào)了一種對(duì)軟件系統(tǒng)普世的原則,即“高內(nèi)聚,低耦合”。盡量減少通信,保持內(nèi)部高度統(tǒng)一。實(shí)際上你去網(wǎng)上搜,該法則也不是完美的,但是它傳遞了一種思想,我認(rèn)為OO設(shè)計(jì)原則的源頭在此。因?yàn)槟阋裱厦滋胤▌t,你就要考慮整個(gè)軟件系統(tǒng)哪些元素應(yīng)該聚合在一起,能夠產(chǎn)生什么行為才是高內(nèi)聚的,如何進(jìn)行交互才是低耦合的。這本身不就是設(shè)計(jì)的過程么?而且我認(rèn)為如果能考慮這些問題,這還很有可能是一個(gè)優(yōu)秀的設(shè)計(jì)。

S.O.L.I.D.原則
Robert Martin有一本非常著名的書,《敏捷軟件開發(fā):原則、模式與實(shí)踐》。他在這里提到SOLID是最初的五個(gè)原則,我感覺這就像是說亞當(dāng)和夏娃是最初的2個(gè)人一樣。其實(shí)還是強(qiáng)調(diào)原則重于模式。我下面會(huì)談?wù)剢我宦氊?zé)和接口隔離,因?yàn)樗鼈冇幸欢ǖ南嗨菩?,也容易掌握。后三個(gè)是OO設(shè)計(jì)的精髓,體現(xiàn)了延遲實(shí)現(xiàn)和針對(duì)接口編程的核心思想。

單一職責(zé)原則
Every context (class, function, variable, etc.) should have a single responsibility, and that responsibility should be entirely encapsulated by the context.(每個(gè)實(shí)體都應(yīng)該只有一種職責(zé),且這種職責(zé)被完全的包裹在該實(shí)體內(nèi)。)

這個(gè)原則相對(duì)簡單,只要你多想想是不是把2個(gè)以上無關(guān)的事情放到了一個(gè)單位里,就可以避免過大而冗余的類。記住,10000行代碼的類,不是你的榮譽(yù),而是你的恥辱。如果10000行的類需要復(fù)用,請(qǐng)問有復(fù)用的可能和切實(shí)可行的辦法么?就一個(gè)類而言,應(yīng)該只有一個(gè)引起它變化的原因。我們經(jīng)常會(huì)遇到User一改需求,就要改同一個(gè)類,即使需求之間沒多大關(guān)聯(lián),這就說明我們違背了單一職責(zé)原則,賦予了一個(gè)類太多的職責(zé)。

接口隔離原則
Once an interface has become too 'fat' it needs to be split into smaller and more specific interfaces so that any clients of the interface will only know about the methods that pertain to them.(一旦一個(gè)接口過于“臃腫”,需要把它拆分成更小和更專一的接口,為的是實(shí)現(xiàn)接口的類,只需要知道和自己相關(guān)的方法。)

對(duì)接口的設(shè)計(jì)同樣要遵循迪米特法則。一旦一個(gè)接口過于“臃腫”,需要把它拆分成更小和更專一的接口,為的是實(shí)現(xiàn)接口的類,只需要知道和自己相關(guān)的方法。最好的例子就是Java中的一些接口定義。比如Java類庫中提供的Comparable和Serializable接口。如果你通過compare方法給出了實(shí)現(xiàn)Comparable接口的類的兩個(gè)對(duì)象的比較結(jié)果,一個(gè)int值。你就可以在一些排序的數(shù)據(jù)結(jié)構(gòu)中很好的承載這些對(duì)象,達(dá)到你比較他們的目的,比TreeTable;Serializable做法更絕,是一個(gè)沒有方法的接口,相當(dāng)于僅僅是一個(gè)帽子,是一個(gè)標(biāo)記,說明只要繼承這個(gè)接口的類才能被序列化,否則就拋出異常。

開/閉原則
Software entities should be open for extension, but closed for modification.(軟件實(shí)體應(yīng)該只做擴(kuò)展,而不做修改。)

開閉原則是最簡單的但很難做到的。繼承應(yīng)當(dāng)被看做是封裝變化的方法,而不應(yīng)當(dāng)被認(rèn)為是從一般的對(duì)象生成特殊的對(duì)象的方法。這是《Java與模式》那本書作者的原話。對(duì)于它的解讀是,完美的繼承是從抽象類到具體類的過程。即具體類通過繼承抽象類而封裝了不同的方法(方法接口在抽象類說明)。錯(cuò)誤的繼承是在一般的對(duì)象基礎(chǔ)上,通過加入特殊的方法,而形成特殊的對(duì)象。

抽象化是開/閉原則的關(guān)鍵。在Java、C#等編程語言中,可以為系統(tǒng)定義一個(gè)相對(duì)穩(wěn)定的抽象層,而將不同的實(shí)現(xiàn)行為移至具體的實(shí)現(xiàn)層中完成。這是第一次提出行為的延后實(shí)現(xiàn),稍后會(huì)看到這個(gè)動(dòng)作的最后落腳點(diǎn)。

里氏替換原則
In a computer program, if S is a subtype of T, then objects of type T may be replaced with objects of type S (i.e., objects of type S may substitute objects of type T) without altering any of the desirable properties of that program (correctness, task performed, etc.).(如果對(duì)每一個(gè)類型為T1的對(duì)象o1,都有類型為T2的對(duì)象o2,使得以T1定義的所有程序P在所有的對(duì)象o1都代換成o2時(shí),程序P的行為沒有變化,那么類型T2是類型T1的子類型。)

里氏替換原則是實(shí)現(xiàn)開/閉原則的重要方式之一,由于使用基類對(duì)象的地方都可以使用子類對(duì)象,因此在程序中盡量使用基類類型來對(duì)對(duì)象進(jìn)行定義,而在運(yùn)行時(shí)再確定其子類類型,用子類對(duì)象來替換父類對(duì)象。

依賴反轉(zhuǎn)原則
Abstractions should not depend upon details. Details should depend upon abstractions. Program to an interface, not an implementation.(抽象不應(yīng)該依賴于具體,具體應(yīng)該依賴于抽象。)

由于有了開/閉原則和里氏替換原則的鋪墊,這里提出了最核心的原則,針對(duì)接口編程,延緩細(xì)節(jié)的實(shí)現(xiàn)。如果說開/閉原則是目標(biāo),里氏替換原則是行為保證,那么按接口編程的依賴反轉(zhuǎn)原則就是有理論保證的,有實(shí)際目標(biāo)的,真正的高質(zhì)量OO設(shè)計(jì)。其實(shí)有人已經(jīng)把該原則叫做OO設(shè)計(jì)的標(biāo)志,足見其重要性。

這三個(gè)重要的OO原則和三個(gè)OO語言特性的本質(zhì)關(guān)系是這樣的:開/閉原則要求我們盡量在構(gòu)造軟件實(shí)體的時(shí)候,應(yīng)該使用擴(kuò)展,而不是修改原來的對(duì)象;那么繼承是一個(gè)很好的方式,繼承在理想化的使用場景中,應(yīng)該是從抽象到具體(將行為封裝到一個(gè)具體對(duì)象之中),而不是從一種具體到另外一種具體。為什么?因?yàn)槔锸咸鎿Q原則要求行為一致,才是繼承的關(guān)系。這和我們理解的加一個(gè)extends就定義了子類和父類的關(guān)系是不同的。由于抽象類沒有具體行為的實(shí)現(xiàn),所以對(duì)抽象類的繼承,天然的是符合里氏替換原則的真正的繼承。而具體到具體的繼承,很難保證里氏替換原則的實(shí)現(xiàn)。

在滿足開/閉原則和里氏替換原則的基礎(chǔ)上,對(duì)同一個(gè)抽象類的行為,不同的實(shí)現(xiàn)了繼承的具體類表現(xiàn)了不同的行為,這就是多態(tài)?;叵氲角懊嫖覀兲岬降腛ffice異質(zhì)鏈表的例子,我們的遍歷操作,是針對(duì)抽象類的行為來進(jìn)行編程的,這就是針對(duì)“接口/抽象類”編程的意義,這也是編程從依賴具體類(Word,Excel和PowerPoint)倒轉(zhuǎn)為依賴Office這個(gè)抽象類的過程。依賴倒轉(zhuǎn)原則的精髓就在于此。

再論重構(gòu)
最后我想再絮叨兩句重構(gòu)的話題。重構(gòu)來源于那本著名的書。那些“壞味道”,也隨重構(gòu)的概念被程序員所熟知。但如果掌握了以上所說的OO設(shè)計(jì)的原則并應(yīng)用于設(shè)計(jì)和實(shí)現(xiàn)階段,那么有些“壞味道”根本就不會(huì)發(fā)生,那么重構(gòu)也不會(huì)發(fā)生了。我把重構(gòu)分為2種,一種是簡單的重構(gòu),就像修改文章中的改正錯(cuò)別字,或者調(diào)整個(gè)別語句的順序;另一種是結(jié)構(gòu)上的重構(gòu),是由于業(yè)務(wù)邏輯的變化或完善,導(dǎo)致設(shè)計(jì)方案的進(jìn)化(注意我并沒有說完全推翻),這時(shí)的重構(gòu)才是最有價(jià)值的。一個(gè)掌握了OO設(shè)計(jì)精髓和原則的程序員,應(yīng)該著眼于結(jié)構(gòu)上的重構(gòu),而在正常的編碼中就要注意避免,數(shù)百行的函數(shù),隨意定義變量和分配內(nèi)存,大量的重復(fù)代碼等問題。賈島有時(shí)間在“推”和“敲”上反復(fù)斟酌,而曹植只有七步的時(shí)間醞釀自己的詩篇,講究的就是一氣呵成。程序員的能力和效率往往就體現(xiàn)在這些不經(jīng)意的地方。所以學(xué)無止境,以此共勉吧。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 設(shè)計(jì)模式之六大原則(轉(zhuǎn)載) 關(guān)于設(shè)計(jì)模式的六大設(shè)計(jì)原則的資料網(wǎng)上很多...
    霄霄霄霄閱讀 960評(píng)論 0 1
  • 設(shè)計(jì)模式六大原則 設(shè)計(jì)模式六大原則(1):單一職責(zé)原則 定義:不要存在多于一個(gè)導(dǎo)致類變更的原因。通俗的說,即一個(gè)類...
    viva158閱讀 828評(píng)論 0 1
  • 轉(zhuǎn)載標(biāo)注聲明:http://www.uml.org.cn/sjms/201211023.asp 目錄:[設(shè)計(jì)模式六...
    Bloo_m閱讀 809評(píng)論 0 7
  • 做后臺(tái)產(chǎn)品的時(shí)候,為了避免設(shè)計(jì)地凌亂,想了解一些系統(tǒng)設(shè)計(jì)模式思想,于是找高內(nèi)聚低耦合相關(guān)的文章。這篇文章是摘自網(wǎng)友...
    徐薇薇閱讀 2,275評(píng)論 0 0
  • IoT是什么 我們先來看看IoT(Internet of Thing 物聯(lián)網(wǎng))是什么。這是一個(gè)被炒作了很久的概念。...
    朱晨閱讀 1,034評(píng)論 0 2

友情鏈接更多精彩內(nèi)容