設(shè)計(jì)模式原則

1.設(shè)計(jì)模式原則:

1.單一職責(zé)原則:一個(gè)類只負(fù)責(zé)一個(gè)功能領(lǐng)域中的相應(yīng)職責(zé),或者可以定義為:就一個(gè)類而言,應(yīng)該只有一個(gè)引起它變化的原因。

一個(gè)類不能太“累”!在軟件系統(tǒng)中,一個(gè)類(大到模塊,小到方法)承擔(dān)的職責(zé)越多,它被復(fù)用的可能性就越小,而且一個(gè)類承擔(dān)的職責(zé)過多,就相當(dāng)于將這些職責(zé)耦合在一起,當(dāng)其中一個(gè)職責(zé)變化時(shí),可能會(huì)影響其他職責(zé)的運(yùn)作,因此要將這些職責(zé)進(jìn)行分離,將不同的職責(zé)封裝在不同的類中,即將不同的變化原因封裝在不同的類中,如果多個(gè)職責(zé)總是同時(shí)發(fā)生改變則可將它們封裝在同一類中。

2.開閉原則:一個(gè)軟件實(shí)體應(yīng)當(dāng)對(duì)擴(kuò)展開放,對(duì)修改關(guān)閉。即軟件實(shí)體應(yīng)盡量在不修改原有代碼的情況下進(jìn)行擴(kuò)展。

任何軟件都需要面臨一個(gè)很重要的問題,即它們的需求會(huì)隨時(shí)間的推移而發(fā)生變化。當(dāng)軟件系統(tǒng)需要面對(duì)新的需求時(shí),我們應(yīng)該盡量保證系統(tǒng)的設(shè)計(jì)框架是穩(wěn)定的。如果一個(gè)軟件設(shè)計(jì)符合開閉原則,那么可以非常方便地對(duì)系統(tǒng)進(jìn)行擴(kuò)展,而且在擴(kuò)展時(shí)無須修改現(xiàn)有代碼,使得軟件系統(tǒng)在擁有適應(yīng)性和靈活性的同時(shí)具備較好的穩(wěn)定性和延續(xù)性。隨著軟件規(guī)模越來越大,軟件壽命越來越長(zhǎng),軟件維護(hù)成本越來越高,設(shè)計(jì)滿足開閉原則的軟件系統(tǒng)也變得越來越重要。

為了滿足開閉原則,需要對(duì)系統(tǒng)進(jìn)行抽象化設(shè)計(jì),抽象化是開閉原則的關(guān)鍵。在Java、C#等編程語言中,可以為系統(tǒng)定義一個(gè)相對(duì)穩(wěn)定的抽象層,而將不同的實(shí)現(xiàn)行為移至具體的實(shí)現(xiàn)層中完成。在很多面向?qū)ο缶幊陶Z言中都提供了接口、抽象類等機(jī)制,可以通過它們定義系統(tǒng)的抽象層,再通過具體類來進(jìn)行擴(kuò)展。如果需要修改系統(tǒng)的行為,無須對(duì)抽象層進(jìn)行任何改動(dòng),只需要增加新的具體類來實(shí)現(xiàn)新的業(yè)務(wù)功能即可,實(shí)現(xiàn)在不修改已有代碼的基礎(chǔ)上擴(kuò)展系統(tǒng)的功能,達(dá)到開閉原則的要求。

3.里氏代換原則(Liskov Substitution Principle, LSP):所有引用基類(父類)的地方必須能透明地使用其子類的對(duì)象

里氏代換原則告訴我們,在軟件中將一個(gè)基類對(duì)象替換成它的子類對(duì)象,程序?qū)⒉粫?huì)產(chǎn)生任何錯(cuò)誤和異常,反過來則不成立,如果一個(gè)軟件實(shí)體使用的是一個(gè)子類對(duì)象的話,那么它不一定能夠使用基類對(duì)象。 例如:我喜歡動(dòng)物,那我一定喜歡狗,因?yàn)楣肥莿?dòng)物的子類;但是我喜歡狗,不能據(jù)此斷定我喜歡動(dòng)物,因?yàn)槲也⒉幌矚g老鼠,雖然它也是動(dòng)物。

例如有兩個(gè)類,一個(gè)類為BaseClass,另一個(gè)是SubClass類,并且SubClass類是BaseClass類的子類,那么一個(gè)方法如果可以接受一個(gè)BaseClass類型的基類對(duì)象base的話,如:method1(base),那么它必然可以接受一個(gè)BaseClass類型的子類對(duì)象sub,method1(sub)能夠正常運(yùn)行。反過來的代換不成立,如一個(gè)方法method2接受BaseClass類型的子類對(duì)象sub為參數(shù):method2(sub),那么一般而言不可以有method2(base),除非是重載方法。

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

在使用里氏代換原則時(shí)需要注意如下幾個(gè)問題:

(1)子類的所有方法必須在父類中聲明,或子類必須實(shí)現(xiàn)父類中聲明的所有方法。根據(jù)里氏代換原則,為了保證系統(tǒng)的擴(kuò)展性,在程序中通常使用父類來進(jìn)行定義,如果一個(gè)方法只存在子類中,在父類中不提供相應(yīng)的聲明,則無法在以父類定義的對(duì)象中使用該方法。

(2)我們?cè)谶\(yùn)用里氏代換原則時(shí),盡量把父類設(shè)計(jì)為抽象類或者接口,讓子類繼承父類或?qū)崿F(xiàn)父接口,并實(shí)現(xiàn)在父類中聲明的方法,運(yùn)行時(shí),子類實(shí)例替換父類實(shí)例,我們可以很方便地?cái)U(kuò)展系統(tǒng)的功能,同時(shí)無須修改原有子類的代碼,增加新的功能可以通過增加一個(gè)新的子類來實(shí)現(xiàn)。里氏代換原則是開閉原則的具體實(shí)現(xiàn)手段之一。

(3)Java語言中,在編譯階段,Java編譯器會(huì)檢查一個(gè)程序是否符合里氏代換原則,這是一個(gè)與實(shí)現(xiàn)無關(guān)的、純語法意義上的檢查,但Java編譯器的檢查是有局限的。

4.依賴倒轉(zhuǎn)原則(DependencyInversionPrinciple,DIP):抽象不應(yīng)該依賴于細(xì)節(jié),細(xì)節(jié)應(yīng)當(dāng)依賴于抽象。換言之,要針對(duì)接口編程,而不是針對(duì)實(shí)現(xiàn)編程

依賴倒轉(zhuǎn)原則要求我們?cè)诔绦虼a中傳遞參數(shù)時(shí)或在關(guān)聯(lián)關(guān)系中,盡量引用層次高的抽象層類,即使用接口和抽象類進(jìn)行變量類型聲明、參數(shù)類型聲明、方法返回類型聲明,以及數(shù)據(jù)類型的轉(zhuǎn)換等,而不要用具體類來做這些事情。為了確保該原則的應(yīng)用,一個(gè)具體類應(yīng)當(dāng)只實(shí)現(xiàn)接口或抽象類中聲明過的方法,而不要給出多余的方法,否則將無法調(diào)用到在子類中增加的新方法。

在引入抽象層后,系統(tǒng)將具有很好的靈活性,在程序中盡量使用抽象層進(jìn)行編程,而將具體類寫在配置文件中,這樣一來,如果系統(tǒng)行為發(fā)生變化,只需要對(duì)抽象層進(jìn)行擴(kuò)展,并修改配置文件,而無須修改原有系統(tǒng)的源代碼,在不修改的情況下來擴(kuò)展系統(tǒng)的功能,滿足開閉原則的要求。

在實(shí)現(xiàn)依賴倒轉(zhuǎn)原則時(shí),我們需要針對(duì)抽象層編程,而將具體類的對(duì)象通過依賴注入(DependencyInjection,DI)的方式注入到其他對(duì)象中,依賴注入是指當(dāng)一個(gè)對(duì)象要與其他對(duì)象發(fā)生依賴關(guān)系時(shí),通過抽象來注入所依賴的對(duì)象。常用的注入方式有三種,分別是:構(gòu)造注入,設(shè)值注入(Setter注入)和接口注入。構(gòu)造注入是指通過構(gòu)造函數(shù)來傳入具體類的對(duì)象,設(shè)值注入是指通過Setter方法來傳入具體類的對(duì)象,而接口注入是指通過在接口中聲明的業(yè)務(wù)方法來傳入具體類的對(duì)象。這些方法在定義時(shí)使用的是抽象類型,在運(yùn)行時(shí)再傳入具體類型的對(duì)象,由子類對(duì)象來覆蓋父類對(duì)象。

5.接口隔離原則(Interface Segregation Principle, ISP):使用多個(gè)專門的接口,而不使用單一的總接口,即客戶端不應(yīng)該依賴那些它不需要的接口。

根據(jù)接口隔離原則,當(dāng)一個(gè)接口太大時(shí),我們需要將它分割成一些更細(xì)小的接口,使用該接口的客戶端僅需知道與之相關(guān)的方法即可。每一個(gè)接口應(yīng)該承擔(dān)一種相對(duì)獨(dú)立的角色,不干不該干的事,該干的事都要干。 這里的“接口”往往有兩種不同的含義:一種是指一個(gè)類型所具有的方法特征的集合,僅僅是一種邏輯上的抽象;另外一種是指某種語言具體的“接口”定義,有嚴(yán)格的定義和結(jié)構(gòu),比如Java語言中的interface。對(duì)于這兩種不同的含義,ISP的表達(dá)方式以及含義都有所不同:

(1)當(dāng)把“接口”理解成一個(gè)類型所提供的所有方法特征的集合的時(shí)候,這就是一種邏輯上的概念,接口的劃分將直接帶來類型的劃分??梢园呀涌诶斫獬山巧粋€(gè)接口只能代表一個(gè)角色,每個(gè)角色都有它特定的一個(gè)接口,此時(shí),這個(gè)原則可以叫做“角色隔離原則”。

(2)如果把“接口”理解成狹義的特定語言的接口,那么ISP表達(dá)的意思是指接口僅僅提供客戶端需要的行為,客戶端不需要的行為則隱藏起來,應(yīng)當(dāng)為客戶端提供盡可能小的單獨(dú)的接口,而不要提供大的總接口。在面向?qū)ο缶幊陶Z言中,實(shí)現(xiàn)一個(gè)接口就需要實(shí)現(xiàn)該接口中定義的所有方法,因此大的總接口使用起來不一定很方便,為了使接口的職責(zé)單一,需要將大接口中的方法根據(jù)其職責(zé)不同分別放在不同的小接口中,以確保每個(gè)接口使用起來都較為方便,并都承擔(dān)某一單一角色。接口應(yīng)該盡量細(xì)化,同時(shí)接口中的方法應(yīng)該盡量少,每個(gè)接口中只包含一個(gè)客戶端(如子模塊或業(yè)務(wù)邏輯類)所需的方法即可,這種機(jī)制也稱為“定制服務(wù)”,即為不同的客戶端提供寬窄不同的接口。

6.合成復(fù)用原則(Composite Reuse Principle, CRP):盡量使用對(duì)象組合,而不是繼承來達(dá)到復(fù)用的目的。

合成復(fù)用原則就是在一個(gè)新的對(duì)象里通過關(guān)聯(lián)關(guān)系(包括組合關(guān)系和聚合關(guān)系)來使用一些已有的對(duì)象,使之成為新對(duì)象的一部分;新對(duì)象通過委派調(diào)用已有對(duì)象的方法達(dá)到復(fù)用功能的目的。簡(jiǎn)言之:復(fù)用時(shí)要盡量使用組合/聚合關(guān)系(關(guān)聯(lián)關(guān)系),少用繼承。

在面向?qū)ο笤O(shè)計(jì)中,可以通過兩種方法在不同的環(huán)境中復(fù)用已有的設(shè)計(jì)和實(shí)現(xiàn),即通過組合/聚合關(guān)系或通過繼承,但首先應(yīng)該考慮使用組合/聚合,組合/聚合可以使系統(tǒng)更加靈活,降低類與類之間的耦合度,一個(gè)類的變化對(duì)其他類造成的影響相對(duì)較少;其次才考慮繼承,在使用繼承時(shí),需要嚴(yán)格遵循里氏代換原則,有效使用繼承會(huì)有助于對(duì)問題的理解,降低復(fù)雜度,而濫用繼承反而會(huì)增加系統(tǒng)構(gòu)建和維護(hù)的難度以及系統(tǒng)的復(fù)雜度,因此需要慎重使用繼承復(fù)用。

通過繼承來進(jìn)行復(fù)用的主要問題在于繼承復(fù)用會(huì)破壞系統(tǒng)的封裝性,因?yàn)槔^承會(huì)將基類的實(shí)現(xiàn)細(xì)節(jié)暴露給子類,由于基類的內(nèi)部細(xì)節(jié)通常對(duì)子類來說是可見的,所以這種復(fù)用又稱“白箱”復(fù)用,如果基類發(fā)生改變,那么子類的實(shí)現(xiàn)也不得不發(fā)生改變;從基類繼承而來的實(shí)現(xiàn)是靜態(tài)的,不可能在運(yùn)行時(shí)發(fā)生改變,沒有足夠的靈活性;而且繼承只能在有限的環(huán)境中使用(如類沒有聲明為不能被繼承)。

7.迪米特法則(Law of Demeter, LoD):一個(gè)軟件實(shí)體應(yīng)當(dāng)盡可能少地與其他實(shí)體發(fā)生相互作用。

如果一個(gè)系統(tǒng)符合迪米特法則,那么當(dāng)其中某一個(gè)模塊發(fā)生修改時(shí),就會(huì)盡量少地影響其他模塊,擴(kuò)展會(huì)相對(duì)容易,這是對(duì)軟件實(shí)體之間通信的限制,迪米特法則要求限制軟件實(shí)體之間通信的寬度和深度。迪米特法則可降低系統(tǒng)的耦合度,使類與類之間保持松散的耦合關(guān)系。

迪米特法則還有幾種定義形式,包括:****不要和“陌生人”說話、只與你的直接朋友通信 等,在迪米特法則中,對(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ì)象。

(1)任何一個(gè)對(duì)象,如果滿足上面的條件之一,就是當(dāng)前對(duì)象的“朋友”,否則就是“陌生人”。在應(yīng)用迪米特法則時(shí),一個(gè)對(duì)象只能與直接朋友發(fā)生交互,不要與“陌生人”發(fā)生直接交互,這樣做可以降低系統(tǒng)的耦合度,一個(gè)對(duì)象的改變不會(huì)給太多其他對(duì)象帶來影響。

(2)迪米特法則要求我們?cè)谠O(shè)計(jì)系統(tǒng)時(shí),應(yīng)該盡量減少對(duì)象之間的交互,如果兩個(gè)對(duì)象之間不必彼此直接通信,那么這兩個(gè)對(duì)象就不應(yīng)當(dāng)發(fā)生任何直接的相互作用,如果其中的一個(gè)對(duì)象需要調(diào)用另一個(gè)對(duì)象的某一個(gè)方法的話,可以通過第三者轉(zhuǎn)發(fā)這個(gè)調(diào)用。簡(jiǎn)言之,就是通過引入一個(gè)合理的第三者來降低現(xiàn)有對(duì)象之間的耦合度。

(3)在將迪米特法則運(yùn)用到系統(tǒng)設(shè)計(jì)中時(shí),要注意下面的幾點(diǎn):在類的劃分上,應(yīng)當(dāng)盡量創(chuàng)建松耦合的類,類之間的耦合度越低,就越有利于復(fù)用,一個(gè)處在松耦合中的類一旦被修改,不會(huì)對(duì)關(guān)聯(lián)的類造成太大波及;在類的結(jié)構(gòu)設(shè)計(jì)上,每一個(gè)類都應(yīng)當(dāng)盡量降低其成員變量和成員函數(shù)的訪問權(quán)限;在類的設(shè)計(jì)上,只要有可能,一個(gè)類型應(yīng)當(dāng)設(shè)計(jì)成不變類;在對(duì)其他類的引用上,一個(gè)對(duì)象對(duì)其他對(duì)象的引用應(yīng)當(dāng)降到最低。

最后編輯于
?著作權(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),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • 設(shè)計(jì)模式的基本原則 設(shè)計(jì)模式的基本原則非常重要,只要真正深入地理解了設(shè)計(jì)原則,很多設(shè)計(jì)模式其實(shí)就是原則的應(yīng)用而已,...
    泥孩兒0107閱讀 285評(píng)論 0 0
  • 1:單一職責(zé)原則 單一職責(zé)原則:一個(gè)類只負(fù)責(zé)一個(gè)功能領(lǐng)域中的相應(yīng)職責(zé),或者可以定義為:就一個(gè)類而言,應(yīng)該只有一個(gè)引...
    千鋒陳老師閱讀 417評(píng)論 0 0
  • 為什么需要設(shè)計(jì)模式 其實(shí)沒有設(shè)計(jì)模式我們也能完成開發(fā)工作。但是為什么需要設(shè)計(jì)模式呢?讓你看起來很牛,沒錯(cuò)這個(gè)算一個(gè)...
    碼農(nóng)小胖哥閱讀 182評(píng)論 0 1
  • 為什么需要設(shè)計(jì)模式 其實(shí)沒有設(shè)計(jì)模式我們也能完成開發(fā)工作。但是為什么需要設(shè)計(jì)模式呢?讓你看起來很牛,沒錯(cuò)這個(gè)算一個(gè)...
    BUG生產(chǎn)者閱讀 104評(píng)論 0 0
  • 設(shè)計(jì)模式(Design Pattern) 是什么是一套被反復(fù)使用、多數(shù)人知曉的、經(jīng)過反復(fù)編寫的、代碼設(shè)計(jì)經(jīng)驗(yàn)的總結(jié)...
    stoneyang94閱讀 374評(píng)論 0 2

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