對(duì)面向?qū)ο笤O(shè)計(jì)原則的學(xué)習(xí)能夠提高大家的系統(tǒng)設(shè)計(jì)能力和代碼編寫質(zhì)量。本文內(nèi)容豐富易懂,對(duì)每一個(gè)面向?qū)ο笤O(shè)計(jì)原則都會(huì)舉出具體的例子來進(jìn)行講解。在文章最后會(huì)對(duì)所有的面向?qū)ο笤O(shè)計(jì)原則進(jìn)行總結(jié)。另外,本篇文章有配套的講解視頻(如果不想看文字就去看視頻叭),請(qǐng)點(diǎn)擊這里觀看(嗶哩嗶哩-小勾狗有什么壞心眼呢-bili_88094125658)。歡迎大家閱讀和觀看~希望通過這篇文章的分享能夠使得大家在今后利用面向?qū)ο笳Z言編寫代碼時(shí)給大家?guī)硪恍椭?/p>一、面向?qū)ο笤O(shè)計(jì)原則提出背景
對(duì)于我們后端來說,平時(shí)大部分時(shí)間都在使用面向?qū)ο笳Z言來設(shè)計(jì)軟件系統(tǒng)。對(duì)于面向?qū)ο筌浖到y(tǒng)的設(shè)計(jì)而言,在支持可維護(hù)性的同時(shí),提高系統(tǒng)的可復(fù)用性是一個(gè)至關(guān)重要的問題,如何同時(shí)提高一個(gè)軟件系統(tǒng)的可維護(hù)性和可復(fù)用性是面向?qū)ο笤O(shè)計(jì)需要解決的核心問題之一。像我們平時(shí)接觸的23種設(shè)計(jì)模式就是以面向?qū)ο笃叽笤瓌t為基礎(chǔ)進(jìn)行設(shè)計(jì)的。所以對(duì)面向?qū)ο笤O(shè)計(jì)的七大原則的學(xué)習(xí)有助于提高我們的設(shè)計(jì)水平,使我們?cè)O(shè)計(jì)的軟件系統(tǒng)有較高的可維護(hù)性和可復(fù)用性。今天呢,我會(huì)通過具體案例,深入淺出精講面向?qū)ο笤O(shè)計(jì)七大原則,讓大家徹底領(lǐng)悟設(shè)計(jì)背后的思想。
二、面向?qū)ο笤O(shè)計(jì)七大原則總覽這張表展示了今天會(huì)分享到的七大原則。下面我會(huì)結(jié)合具體的例子來一一分享各個(gè)原則。
設(shè)計(jì)原則名稱 | 設(shè)計(jì)原則簡介 |
---|---|
單一職責(zé)原則(Single Responsibility Principle, SRP) | 類的職責(zé)要單一,不能將太多的職責(zé)放在一個(gè)類中 |
開閉原則(Open-Closed Principle, OCP) | 軟件實(shí)體對(duì)擴(kuò)展是開放的,但對(duì)修改是關(guān)閉的,即在不修改一個(gè)軟件實(shí)體的基礎(chǔ)上去擴(kuò)展其功能 |
里氏代換原則(Liskov Substitution Principle, LSP) | 在軟件系統(tǒng)中,一個(gè)可以接受基類對(duì)象的地方必然可以接受一個(gè)子類對(duì)象 |
依賴倒轉(zhuǎn)原則(Dependency Inversion Principle, DIP) | 要針對(duì)抽象層編程,而不要針對(duì)具體類編程 |
接口隔離原則(Interface Segregation Principle, ISP) | 使用多個(gè)專門的接口來取代一個(gè)統(tǒng)一的接口 |
合成復(fù)用原則(Composite Reuse Principle, CRP) | 在系統(tǒng)中應(yīng)該盡量多使用組合和聚合關(guān)聯(lián)關(guān)系,盡量少使用甚至不使用繼承關(guān)系 |
迪米特法則(Law of Demeter, LoD) | 一個(gè)軟件實(shí)體對(duì)其他實(shí)體的引用越少越好,或者說如果兩個(gè)類不必彼此直接通信,那么這兩個(gè)類就不應(yīng)當(dāng)發(fā)生直接的相互作用,而是通過引入一個(gè)第三者發(fā)生間接交互 |
首先來看第一個(gè)原則,單一職責(zé)原則。單一職責(zé)原則有兩種定義方式。
第一種定義是一個(gè)對(duì)象應(yīng)該只包含單一的職責(zé),并且該職責(zé)被完整地封裝在一個(gè)類中。(Every object should have a single responsibility, and that responsibility should be entirely encapsulated by the class.)
第二種定義是就一個(gè)類而言,應(yīng)該僅有一個(gè)引起它變化的原因。(There should never be more than one reason for a class to change.)
下面我們做一個(gè)具體分析。類的職責(zé)主要包括兩個(gè)方面:數(shù)據(jù)職責(zé)和行為職責(zé),數(shù)據(jù)職責(zé)通過其屬性來體現(xiàn),而行為職責(zé)通過其方法來體現(xiàn)。顯而易見,一個(gè)類(或者大到模塊,小到方法)承擔(dān)的職責(zé)越多,它被復(fù)用的可能性越小,而且如果一個(gè)類承擔(dān)的職責(zé)過多,就相當(dāng)于將這些職責(zé)耦合在一起,當(dāng)其中一個(gè)職責(zé)變化時(shí),可能會(huì)影響其他職責(zé)的運(yùn)作。
3.3舉例:下面通過舉例Java的C/S系統(tǒng)的“登錄功能”來說明單一職責(zé)原則。在圖中,登錄類中的init方法是初始化方法、display是顯示方法、validate是數(shù)據(jù)驗(yàn)證方法、getConnection是鏈接數(shù)據(jù)庫的方法、findUser是通過數(shù)據(jù)庫校驗(yàn)用戶名和密碼的方法、main方法是整個(gè)系統(tǒng)運(yùn)行的方法?,F(xiàn)在我們來看看他是不是符合單一職責(zé)原則。單一職責(zé)原則是一個(gè)類應(yīng)該只包含一個(gè)單一的職責(zé),而現(xiàn)在我們看到,Login類中其實(shí)包含了三種職責(zé)。首先是init方法、display方法和validate方法組成的展示頁面的職責(zé)、然后是getConnection方法和findUser方法組成的查詢數(shù)據(jù)庫的職責(zé)、最后是main方法構(gòu)成的運(yùn)行整個(gè)系統(tǒng)的職責(zé)。我們說就一個(gè)類而言,應(yīng)該僅有一個(gè)引起它變化的原因。如果說我們現(xiàn)在需要對(duì)顯示做變換,那么我們需要修改display方法從而導(dǎo)致Login類被修改、而如果我們說現(xiàn)在需要修改數(shù)據(jù)庫的鏈接方式,那么我們需要修改getConnection方法同樣會(huì)導(dǎo)致Login類被修改,再如果說我們要修改main方法比如在運(yùn)行前需要添加一下處理,那么就需要修改Login類種main方法。所以說Login類現(xiàn)在能引起它變化的原因有很多種。這顯然不太好。
現(xiàn)在我們使用單一職責(zé)原則對(duì)其進(jìn)行重構(gòu)。對(duì)于這四個(gè)類的介紹,可以點(diǎn)擊這里觀看視頻(在5分00秒處)?,F(xiàn)在我們看到我們把Login類劃分成了四個(gè)不同的類,每個(gè)類有自己的單一職責(zé),也就是說每一個(gè)類現(xiàn)在引起他們變換的原因都只有一個(gè)。也就是說現(xiàn)在四個(gè)類是四個(gè)正交的維度,他們之間的變換是無關(guān)的。像現(xiàn)在這樣的設(shè)計(jì)就符合了單一職責(zé)原則。這樣當(dāng)因?yàn)樽兓枰薷臅r(shí),就可以只修改對(duì)應(yīng)的模塊而不用影響其它的模塊。可以看到現(xiàn)在的設(shè)計(jì),在可維護(hù)性和可復(fù)用性上都比之前的設(shè)計(jì)好很多了。比如說我們想更改getConnection方法,那么我們只會(huì)去修改UserDAO類,而其它類不會(huì)受到影響。而且引起UserDAO發(fā)生改變的原因只有一個(gè),而不是很多個(gè)。
第二個(gè)原則,開閉原則。開閉原則定義是一個(gè)軟件實(shí)體應(yīng)當(dāng)對(duì)擴(kuò)展開放,對(duì)修改關(guān)閉。(Software entities should be open for extension, but closed for modification.)也就是說在設(shè)計(jì)一個(gè)模塊的時(shí)候,應(yīng)當(dāng)使這個(gè)模塊可以在不被修改的前提下被擴(kuò)展,即實(shí)現(xiàn)在不修改源代碼的情況下改變這個(gè)模塊的行為。
4.2分析:開閉原則由Bertrand Meyer于1988年提出,也就是下面圖片中的這個(gè)人,旁邊這兩本經(jīng)典的書就是他寫的。開閉原則是面向?qū)ο笤O(shè)計(jì)中最重要的原則之一。在開閉原則的定義中,軟件實(shí)體可以指一個(gè)軟件模塊、一個(gè)由多個(gè)類組成的局部結(jié)構(gòu)或一個(gè)獨(dú)立的類。在開閉原則中,抽象化是它的關(guān)鍵。開閉原則還可以通過一個(gè)更加具體的“對(duì)可變性封裝原則”(Principle of Encapsulation of Variation, EVP)來描述,其中“對(duì)可變性封裝原則”要求找到系統(tǒng)的可變因素并將其封裝起來。
現(xiàn)在通過舉例圖像界面系統(tǒng)中各種不同的按鈕來說明開閉原則的應(yīng)用。某圖形界面系統(tǒng)提供了各種不同形狀的按鈕,客戶端代碼可針對(duì)這些按鈕進(jìn)行編程,用戶可能會(huì)改變需求要求使用不同的按鈕,原始設(shè)計(jì)方案如圖所示。其中LoginForm是登錄頁面,包含一個(gè)button按鈕,display顯示方法會(huì)根據(jù)不同類型的button按鈕展示不同樣式的button。可以看到,每當(dāng)需要變換按鈕時(shí),需要修改LoginForm中的代碼,而這違反了開閉原則中所說的對(duì)修改關(guān)閉。另外,如果用戶需要新的Button時(shí),需要重新定義一個(gè)button然后修改LoginForm的代碼,這對(duì)擴(kuò)展很不方便?,F(xiàn)在我們需要思考如何更改設(shè)計(jì),來達(dá)到在不修改代碼的情況下做到擴(kuò)展。
現(xiàn)在我們使用開閉原則對(duì)其進(jìn)行重構(gòu)。重新構(gòu)造后的結(jié)構(gòu)如圖所示。對(duì)于這張圖的介紹,可以點(diǎn)擊這里觀看視頻(在9分37秒處)。現(xiàn)在,當(dāng)我們需要變換按鈕時(shí)只需要修改配置文件就可以改變按鈕的樣式了,另外,如果有新的按鈕,我們只需要增加一個(gè)新button類繼承AbstractButton,然后修改配置文件就可以很方便地進(jìn)行擴(kuò)展了,可以看到我們并沒有修改任何LoginForm中的代碼就實(shí)現(xiàn)了擴(kuò)展,現(xiàn)在的設(shè)計(jì)比之前的設(shè)計(jì)好了很多。
這里的配置文件可以讓LoginForm中AbstractButton類型的button對(duì)象是配置文件中指定的類(例如圖中是CircleButton)的實(shí)例。
第三個(gè)原則,里氏替換原則。里氏替換原則有兩種方式的定義,第一個(gè)定義是:如果對(duì)每一個(gè)類型為S的對(duì)象o1,都有類型為T的對(duì)象o2,使得以T定義的所有程序P在所有的對(duì)象o2都代換成o1時(shí),程序P的行為沒有變化,那么類型S是類型T的子類型。(If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T.)
第二定義是:所有引用基類(父類)的地方必須能透明地使用其子類的對(duì)象。(Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.)
其中定義一比較重要的一點(diǎn)是替換后行為沒有發(fā)生變化,也就是說如果替換后行為發(fā)生了變化或者代碼運(yùn)行時(shí)出現(xiàn)行為/業(yè)務(wù)錯(cuò)誤,那么這兩個(gè)類就不應(yīng)該是父子關(guān)系。定義二中比較重要的是透明這一個(gè)詞,透明的含義是指,當(dāng)基類被子類替換時(shí),感覺不到基類被子類替換了,也就是行為不會(huì)發(fā)生變化。
5.2分析:里氏代換原則由2008年圖靈獎(jiǎng)得主、美國第一位計(jì)算機(jī)科學(xué)女博士、麻省理工學(xué)院教授Barbara Liskov和卡內(nèi)基.梅隆大學(xué)Jeannette Wing教授于1994年提出。也就是下面這兩位。里氏代換原則可以通俗表述為:在軟件中如果能夠使用基類對(duì)象,那么一定能夠使用其子類對(duì)象。把基類都替換成它的子類,程序?qū)⒉粫?huì)產(chǎn)生任何錯(cuò)誤和異常,反過來則不成立,如果一個(gè)軟件實(shí)體使用的是一個(gè)子類的話,那么它不一定能夠使用基類。里氏代換原則是實(shí)現(xiàn)開閉原則的重要方式之一,由于使用基類對(duì)象的地方都可以使用子類對(duì)象,因此在程序中盡量使用基類類型來對(duì)對(duì)象進(jìn)行定義,而在運(yùn)行時(shí)再確定其子類類型,用子類對(duì)象來替換父類對(duì)象。
現(xiàn)在通過舉例對(duì)數(shù)據(jù)進(jìn)行加密來說明里氏替換原則的應(yīng)用。(這里舉例比較復(fù)雜,建議觀看視頻(在13分57秒處)。)如圖所示,某系統(tǒng)現(xiàn)在需要對(duì)重要數(shù)據(jù),如用戶密碼,進(jìn)行加密處理,在數(shù)據(jù)操作類DataOperator中需要調(diào)用加密類中定義的加密算法encrypt,系統(tǒng)提供了兩個(gè)不同的加密類,CipherA和CipherB,它們實(shí)現(xiàn)不同的加密方法,在DataOperator中可以選擇其中一個(gè)實(shí)現(xiàn)加密操作?,F(xiàn)在考慮一個(gè)問題,如果需要更換一個(gè)加密算法類或者增加并使用一個(gè)新的加密算法類,如將CipherA改為CipherB(Client中的main方法需要修改為new CipherB,setCipherB,)或添加一個(gè)新的加密算法CipherC(DataOperator中的encrypt方法需要添加ifelse、DataOperator類還要添加一個(gè)CipherC、main方法也需要修改),則需要修改客戶類Client和數(shù)據(jù)操作類DataOperator的源代碼,這違背了開閉原則。
Client中的main方法:
DataOperator中的setCipherA方法:
DataOperator中的encrypt方法:
現(xiàn)在使用里氏代換原則對(duì)其進(jìn)行重構(gòu),使得系統(tǒng)可以靈活擴(kuò)展,符合開閉原則。重構(gòu)后的結(jié)構(gòu)如圖所示,當(dāng)需要修改加密算法時(shí),可以直接需改配置文件;如果需要擴(kuò)展加密算法,可以添加新的加密算法繼承CipherA類,然后修改Config配置文件就行,保證了對(duì)修改關(guān)閉對(duì)擴(kuò)展開放??梢钥吹酵ㄟ^里氏代換原則確保了開閉原則。從而提高了系統(tǒng)的可維護(hù)性和可復(fù)用性。
Client中的main方法:
另外,里氏替換原則還可以用來判別兩個(gè)類是否應(yīng)該為父子關(guān)系。下面看某公司給員工計(jì)算報(bào)酬的例子。其中{A}代表是一個(gè)抽象類。Employee是員工類,它是領(lǐng)月薪員工和領(lǐng)時(shí)薪員工的父類,Timecard是用來記錄時(shí)薪員工的小時(shí)數(shù)。Employee中的抽象方法calcPay方法用來計(jì)算工資,由各個(gè)子類負(fù)責(zé)具體實(shí)現(xiàn)。
現(xiàn)在考慮如果要增加一個(gè)VolunteerEmployee志愿者員工類繼承至Employee類,即這類員工沒有薪水,那么calcPay該怎么實(shí)現(xiàn)
第一種方法,return 0:
第一種方法返回return 0,但是這樣沒有意義,因?yàn)槿绻谄渌胤接幸粋€(gè)轉(zhuǎn)賬方法,那么它會(huì)給志愿者轉(zhuǎn)賬0元,沒有實(shí)際意義,志愿者打開手機(jī)銀行一看給我轉(zhuǎn)賬0元是什么意思?
第二種方法,拋異常:
第二種方法拋異常。如果拋異常的話要么異常必須被捕獲,要么調(diào)用者說明,因此,在一個(gè)派生類上的約束已經(jīng)影響到了基類用戶。
另外如果使用第二種方法,那原來的計(jì)算所有員工工資的代碼會(huì)這樣變動(dòng):
原來的代碼:
變動(dòng)后的代碼:
使用try catch語句
或者
使用instaceof判斷是哪個(gè)類型,但這樣更糟糕,因?yàn)樵瓉砘贓mployee基類的代碼,現(xiàn)在必須要明確引用它的一個(gè)子類VolunteerEmployee
但不管哪樣變動(dòng),都影響到了其它地方。其根源在于違背了里氏替換原則,因?yàn)閂olunteerEmployee不能和Employee是父子關(guān)系。如果是父子關(guān)系會(huì)違背里氏替換原則,VolunteerEmployee不能透明地替代Employee。只要當(dāng)調(diào)用一個(gè)派生類上的方法時(shí)造成了非法使用,就會(huì)違反里氏替換原則;如果使用了一個(gè)退化的派生類的方法(什么也沒實(shí)現(xiàn)),也是違反里氏替換原則。此時(shí)說明該方法對(duì)派生類是無意義的。繼承應(yīng)該是關(guān)于行為的,而不是志愿者員工從直覺上判斷就是員工類,而是從行為上來判斷是否屬于員工類。很明顯這里的員工類是計(jì)算薪水的員工,而志愿者員工不計(jì)算薪水,所以志愿者員工不應(yīng)該繼承員工類。通過里氏替換原則可以幫助我們來判斷兩個(gè)類是否應(yīng)該用繼承關(guān)系。
六、依賴倒轉(zhuǎn)原則(DIP) 6.1定義:第四個(gè)原則,依賴倒轉(zhuǎn)原則。依賴倒轉(zhuǎn)原則有兩種方式的定義。
第一種定義是:高層模塊不應(yīng)該依賴低層模塊,它們都應(yīng)該依賴抽象。抽象不應(yīng)該依賴于細(xì)節(jié),細(xì)節(jié)應(yīng)該依賴于抽象。(High level modules should not depend upon low level modules, both should depend upon abstractions. Abstractions should not depend upon details, details should depend upon abstractions.)
第二種定義是:要針對(duì)接口編程,不要針對(duì)實(shí)現(xiàn)編程。(Program to an interface, not an implementation.)
6.2分析:依賴倒轉(zhuǎn)原則是Robert C. Martin在1996年為《C++ Reporter》所寫的專欄Engineering Notebook的第三篇,后來加入到他在2002年出版的經(jīng)典著作《Agile Software Development, Principles, Patterns, and Practices》中。也就是下面這個(gè)人和他寫的經(jīng)典書籍。簡單來說,依賴倒轉(zhuǎn)原則就是指:代碼要依賴于抽象的類,而不要依賴于具體的類;要針對(duì)接口或抽象類編程,而不是針對(duì)具體類編程。依賴倒轉(zhuǎn)原則要求客戶端依賴于抽象耦合,以抽象方式耦合是依賴倒轉(zhuǎn)原則的關(guān)鍵。
6.3舉例:現(xiàn)在通過舉例來說明依賴倒置原則的應(yīng)用。如圖中所示,現(xiàn)在代碼一共有三層,高層模塊Policy layer是策略層,中層模塊Mechanism Layer是機(jī)制層,它對(duì)策略層進(jìn)一步實(shí)現(xiàn),底層模塊Utility Layer是工具層,提供一些工具層的東西給中層模塊使用??梢钥吹?,高層調(diào)用了中層然后中層又調(diào)用了底層,越往下相對(duì)于上一層實(shí)現(xiàn)的越具體(上一層相當(dāng)于抽象,下一層相當(dāng)于具體,抽象依賴于具體)。這樣的設(shè)計(jì)看起來似乎是正確的,但實(shí)際上是不好的。因?yàn)楦邔幽KPolicy layer對(duì)下面的兩個(gè)層的改動(dòng)都很敏感,中層模塊對(duì)底層模塊的改動(dòng)很敏感。如果底層模塊有改動(dòng),那么中層模塊可能也會(huì)隨之改動(dòng)從而可能導(dǎo)致高層也會(huì)改動(dòng)。
現(xiàn)在我們使用依賴倒置原則對(duì)其進(jìn)行重構(gòu)。重新構(gòu)造后的結(jié)構(gòu)如圖所示。我們?cè)诿恳粚犹砑映橄蠡慕涌?,使下一層?shí)現(xiàn)這個(gè)接口。這樣當(dāng)下一層發(fā)生改變時(shí),因?yàn)樯弦粚雍拖乱粚又虚g的接口沒有發(fā)生改變所以不會(huì)影響到上一層,上一層只依賴于抽象的接口而不是下一層具體的實(shí)現(xiàn)。這樣就降低了模塊間的耦合,提高了系統(tǒng)的可維護(hù)性和可復(fù)用性。
第五個(gè)原則,接口隔離原則。接口隔離原則有兩種方式的定義。
第一種定義是:客戶端不應(yīng)該依賴那些它不需要的接口。(Clients should not be forced to depend upon interfaces that they do not use.)
第二種定義是:一旦一個(gè)接口太大,則需要將它分割成一些更細(xì)小的接口,使用該接口的客戶端僅需知道與之相關(guān)的方法。(Once an interface has gotten 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.)
7.2分析:接口隔離原則是指使用多個(gè)專門的接口,而不使用單一的總接口。每一個(gè)接口應(yīng)該承擔(dān)一種相對(duì)獨(dú)立的角色,不多不少,不干不該干的事,該干的事都要干。使用接口隔離原則拆分接口時(shí),首先必須滿足單一職責(zé)原則,將一組相關(guān)的操作定義在一個(gè)接口中,且在滿足高內(nèi)聚的前提下,接口中的方法越少越好??梢栽谶M(jìn)行系統(tǒng)設(shè)計(jì)時(shí)采用定制服務(wù)的方式,即為不同的客戶端提供寬窄不同的接口,只提供用戶需要的行為,而隱藏用戶不需要的行為。
7.3舉例:現(xiàn)在通過客戶系統(tǒng)來說明接口隔離原則的應(yīng)用。如圖展示了一個(gè)擁有多個(gè)客戶類的系統(tǒng),在系統(tǒng)中定義了一個(gè)巨大的接口(胖接口)AbstractService來服務(wù)所有的客戶類??梢钥吹綄?duì)于ClienA類,AbstractService在提供operatorA方法的同時(shí)還提供了它不需要的operatorB和operatorC方法,對(duì)于ClienB類和ClienC類均是如此。顯而易見,這并不合理。因?yàn)橐皇墙涌诘膶?shí)現(xiàn)類龐大,實(shí)現(xiàn)類中需要實(shí)現(xiàn)接口中的所有方法,靈活性較差,如果某些方法不實(shí)現(xiàn)而是大量空方法,將導(dǎo)致系統(tǒng)中產(chǎn)生很多無用的代碼,影響代碼的質(zhì)量。另外由于客戶端針對(duì)大接口編程,將在一定程度上破壞程序的封裝性,客戶端看到了不應(yīng)該看到的方法,沒有為客戶端定制接口。這樣的設(shè)計(jì)結(jié)構(gòu)違背了接口隔離原則。
現(xiàn)在我們使用接口隔離原則對(duì)其進(jìn)行重構(gòu)??梢钥吹轿覀儗bstractService進(jìn)行了分割,分割成了多個(gè)細(xì)小的接口。對(duì)于每一個(gè)客戶類,只需要依賴它需要的接口就行了。如此以來不同的客戶類使用不同的接口,只提供客戶類需要的行為,隱藏了客戶類不需要的行為。這樣的設(shè)計(jì)顯然是合理的。
接下來要介紹的是迪米特法則(Law of Demeter, LoD)又稱為最少知識(shí)原則(Least Knowledge Principle, LKP),它有多種定義方法,其中幾種典型定義如下。
第一種定義是不要和“陌生人”說話(Don’t talk to strangers.)。
第二種定義是只與你的直接朋友通信(Talk only to your immediate friends.)。
第三種定義是每一個(gè)軟件單位對(duì)其他的單位都只有最少的知識(shí),而且局限于那些與本單位密切相關(guān)的軟件單位(Each unit should have only limited knowledge about other units: only units “closely” related to the current unit.)。
簡單來說,迪米特法則就是指一個(gè)軟件實(shí)體應(yīng)當(dāng)盡可能少的與其他實(shí)體發(fā)生相互作用。這樣,當(dāng)一個(gè)模塊修改時(shí),就會(huì)盡量少的影響其他的模塊,擴(kuò)展會(huì)相對(duì)容易,這是對(duì)軟件實(shí)體之間通信的限制,它要求限制軟件實(shí)體之間通信的寬度和深度。
在迪米特法則中,對(duì)于一個(gè)對(duì)象,其朋友包括以下幾類:
(1) 當(dāng)前對(duì)象本身(this);
(2) 以參數(shù)形式傳入到當(dāng)前對(duì)象方法中的對(duì)象;
(3) 當(dāng)前對(duì)象的成員對(duì)象;
(4) 如果當(dāng)前對(duì)象的成員對(duì)象是一個(gè)集合,那么集合中的元素也都是朋友;
(5) 當(dāng)前對(duì)象所創(chuàng)建的對(duì)象。任何一個(gè)對(duì)象,如果滿足上面的條件之一,就是當(dāng)前對(duì)象的“朋友”,否則就是“陌生人”。
在狹義的迪米特法則中,如果兩個(gè)類之間不必彼此直接通信,那么這兩個(gè)類就不應(yīng)當(dāng)發(fā)生直接的相互作用,如果其中的一個(gè)類需要調(diào)用另一個(gè)類的某一個(gè)方法的話,可以通過第三者轉(zhuǎn)發(fā)這個(gè)調(diào)用。下面這張圖的介紹請(qǐng)觀看視頻(在33分48秒處)
迪米特法則的主要用途在于控制信息的過載:
1.在類的劃分上,應(yīng)當(dāng)盡量創(chuàng)建松耦合的類,類之間的耦合度越低,就越有利于復(fù)用,一個(gè)處在松耦合中的類一旦被修改,不會(huì)對(duì)關(guān)聯(lián)的類造成太大波及;
2.在類的結(jié)構(gòu)設(shè)計(jì)上,每一個(gè)類都應(yīng)當(dāng)盡量降低其成員變量和成員函數(shù)的訪問權(quán)限;
3.在類的設(shè)計(jì)上,只要有可能,一個(gè)類型應(yīng)當(dāng)設(shè)計(jì)成不變類;
4.在對(duì)其他類的引用上,一個(gè)對(duì)象對(duì)其他對(duì)象的引用應(yīng)當(dāng)降到最低。
現(xiàn)在通過舉例來說明迪米特法則的應(yīng)用。如圖展示了某系統(tǒng)界面類(如Form1、Form2等類)與數(shù)據(jù)訪問類(如DAO1、DAO2等類)之間的調(diào)用,可以從圖中看到它們的調(diào)用關(guān)系較為復(fù)雜。迪米特法則強(qiáng)調(diào)不要有太多的緊耦合而是應(yīng)該是采用松耦合,一個(gè)軟件實(shí)體應(yīng)當(dāng)盡可能少的與其他實(shí)體發(fā)生相互作用??梢钥吹?,F(xiàn)orm3與DAO2-4這三個(gè)DAO都有關(guān)聯(lián),其中有一個(gè)發(fā)生變換都會(huì)影響到Form3。另外DAO2發(fā)生改變除了會(huì)影響Form3還會(huì)影響Form4,Form5,所以下面的設(shè)計(jì)是一個(gè)不太好的設(shè)計(jì)。
現(xiàn)在我們使用迪米特法則對(duì)其進(jìn)行重構(gòu)。重構(gòu)的結(jié)果如圖所示。可以看到,我們?cè)贔orm和DAO的中間添加了一個(gè)中間層Controller,這樣就降低了系統(tǒng)的耦合。Form通過中間的Controller與DAO發(fā)生間接的通信。這樣當(dāng)DAO發(fā)生改變時(shí)就不會(huì)影響到Form,達(dá)到了Form盡可能少的與其他實(shí)體發(fā)生相互作用的目的。
最后一個(gè)原則,合成復(fù)用原則。合成復(fù)用的定義是盡量使用對(duì)象組合,而不是繼承來達(dá)到復(fù)用的目的。(Favor composition of objects over inheritance as a reuse mechanism.)
9.2分析:合成復(fù)用原則就是指在一個(gè)新的對(duì)象里通過關(guān)聯(lián)關(guān)系(包括組合關(guān)系和聚合關(guān)系)來使用一些已有的對(duì)象,使之成為新對(duì)象的一部分;新對(duì)象通過委派調(diào)用已有對(duì)象的方法達(dá)到復(fù)用其已有功能的目的。簡言之:要盡量使用組合/聚合關(guān)系,少用繼承。因?yàn)橥ㄟ^繼承來復(fù)用,雖然實(shí)現(xiàn)簡單。但破壞系統(tǒng)的封裝性;從基類繼承而來的實(shí)現(xiàn)是靜態(tài)的,不可能在運(yùn)行時(shí)發(fā)生改變,沒有足夠的靈活性;只能在有限的環(huán)境中使用。(“白箱”復(fù)用 )而通過組合/聚合來復(fù)用,可以使耦合度相對(duì)較低,選擇性地調(diào)用成員對(duì)象的操作;可以在運(yùn)行時(shí)動(dòng)態(tài)進(jìn)行。(“黑箱”復(fù)用 )
9.3舉例:現(xiàn)在舉例來說明接口隔離原則的應(yīng)用。某教學(xué)管理系統(tǒng)部分?jǐn)?shù)據(jù)庫訪問類設(shè)計(jì)如圖所示,可以看到StudentDAO和TeachearDAO通過繼承來復(fù)用DBUtil中的getConnection方法來鏈接數(shù)據(jù)庫,首先可以發(fā)現(xiàn)DBUtil和Student/TeachearDAO不是is-a的關(guān)系,也就是從語義來說不應(yīng)該繼承關(guān)系。另外,如果需要更換數(shù)據(jù)庫連接方式,如原來采用JDBC連接數(shù)據(jù)庫,現(xiàn)在采用數(shù)據(jù)庫連接池連接,則需要修改DBUtil類源代碼。如果StudentDAO采用JDBC連接,但是TeacherDAO采用連接池連接,則需要增加一個(gè)新的DBUtil類,并修改StudentDAO或TeacherDAO的源代碼,使之繼承新的數(shù)據(jù)庫連接類,這將違背開閉原則,系統(tǒng)擴(kuò)展性較差。
現(xiàn)在我們使用合成復(fù)用原則對(duì)其進(jìn)行重構(gòu)。重構(gòu)的結(jié)果如圖所示??梢钥吹竭@一次我們采用了聚合的方式來復(fù)用DBUtil。這樣一來當(dāng)我們需要換數(shù)據(jù)庫鏈接方法時(shí),可以創(chuàng)建一個(gè)新類NewDBUtil來繼承DBUtil從而在滿足開閉原則的前提下,對(duì)功能進(jìn)行擴(kuò)展。可以看到新的設(shè)計(jì)結(jié)構(gòu)提高了系統(tǒng)的可維護(hù)性和可復(fù)用性。
七大原則本質(zhì)上指導(dǎo)了兩件事情,第一件事情是一個(gè)類該如何設(shè)計(jì),第二件事情是兩個(gè)類關(guān)系該如何設(shè)計(jì)。對(duì)于一個(gè)類該如何設(shè)計(jì)主要遵循單一職責(zé)原則,即類的職責(zé)要單一,不能將太多的職責(zé)放在一個(gè)類中,類的變化點(diǎn)要盡可能少和開閉原則,即軟件實(shí)體對(duì)擴(kuò)展是開放的,但對(duì)修改是關(guān)閉的,即在不修改一個(gè)軟件實(shí)體的基礎(chǔ)上去擴(kuò)展其功能。這兩個(gè)原則使得一個(gè)類滿足高內(nèi)聚。對(duì)于2個(gè)類關(guān)系該如何設(shè)計(jì),從策略上來講,最鼓勵(lì)我們?nèi)プ裱氖紫仁堑厦滋胤▌t,即兩個(gè)類盡量不要發(fā)生關(guān)系,要盡量少的去發(fā)生關(guān)系;如果非要發(fā)生關(guān)系,鼓勵(lì)遵循依賴倒置原則,要依賴抽象,和抽象發(fā)生關(guān)系而不是具體的類發(fā)生關(guān)系,即要針對(duì)抽象層編程,而不要針對(duì)具體類編程;接著就是,若發(fā)生依賴關(guān)系,要注意接口隔離原則,即,使用多個(gè)專門的接口來取代一個(gè)統(tǒng)一的接口,要依賴也僅僅依賴自己需要的服務(wù);如果現(xiàn)在有復(fù)用的需要,應(yīng)該優(yōu)先使用關(guān)聯(lián)關(guān)系而不是繼承,即遵循合成復(fù)用原則;如果關(guān)聯(lián)關(guān)系滿足不了要求,非要用繼承,那么此時(shí)就應(yīng)該遵循里氏替換原則,即所有引用基類(父類)的地方必須能透明地使用其子類的對(duì)象。迪米特法則、依賴倒置原則、接口最小隔離原則、合成復(fù)用原則和里氏替換原則使得類之間滿足松耦合。
可見遵循面向?qū)ο笤O(shè)計(jì)的七大原則可以使我們的系統(tǒng)實(shí)現(xiàn)高內(nèi)聚松耦合并且能夠提高系統(tǒng)的可維護(hù)性和可復(fù)用性,希望通過今天的分享能夠使得大家在今后利用面向?qū)ο笳Z言編寫代碼時(shí)給大家?guī)硪恍椭?,使得在設(shè)計(jì)系統(tǒng)時(shí)更合理,編寫代碼時(shí)質(zhì)量更高,以上就是今天給大家分享的全部內(nèi)容,感謝大家的閱讀。
你是否還在尋找穩(wěn)定的海外服務(wù)器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機(jī)房具備T級(jí)流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確流量調(diào)度確保服務(wù)器高可用性,企業(yè)級(jí)服務(wù)器適合批量采購,新人活動(dòng)首月15元起,快前往官網(wǎng)查看詳情吧
當(dāng)前文章:深入淺出精講面向?qū)ο笤O(shè)計(jì)七大原則,徹底領(lǐng)悟設(shè)計(jì)背后思想-創(chuàng)新互聯(lián)
文章來源:http://aaarwkj.com/article8/goiip.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供電子商務(wù)、動(dòng)態(tài)網(wǎng)站、標(biāo)簽優(yōu)化、定制開發(fā)、企業(yè)網(wǎng)站制作、網(wǎng)站維護(hù)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容