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

軟件開(kāi)發(fā)是始于面向過(guò)程的


軟件開(kāi)發(fā)是始于面向過(guò)程的,因?yàn)槊嫦蜻^(guò)程地解決問(wèn)題更直接,軟件本身就是一個(gè)解決問(wèn)題的過(guò)程;面向過(guò)程的最大問(wèn)題就是不容易把問(wèn)題進(jìn)行分解,再大的問(wèn)題都要在一個(gè)過(guò)程里面解決,從第一步直到最后一步;最多是把一個(gè)大過(guò)程分解成幾個(gè)順序執(zhí)行的小過(guò)程,開(kāi)發(fā)出的軟件不容易維護(hù)、重用和擴(kuò)展。面象對(duì)象的方法沒(méi)那么直接,需要有一個(gè)抽象的過(guò)程,要把問(wèn)題抽象成一個(gè)個(gè)對(duì)象,每個(gè)對(duì)象解決一個(gè)小問(wèn)題,不同對(duì)象的組合就可解決不同的大問(wèn)題;而且把對(duì)象跟日常的事物聯(lián)系起來(lái),產(chǎn)生了屬性、事件、方法這樣的概念,增加了對(duì)象的直觀性。

面向?qū)ο箝_(kāi)發(fā)(OOP)是基礎(chǔ)


OOP是以對(duì)象模型反應(yīng)領(lǐng)域內(nèi)的實(shí)體之間的關(guān)系,其本質(zhì)是接近于人類認(rèn)知事物所采用的哲學(xué)觀的計(jì)算模型;在OOP中,對(duì)象作為計(jì)算主體,擁有自己的名稱,狀態(tài)及接受外界消息的接口。在對(duì)象模型中,產(chǎn)生新對(duì)象,銷毀舊對(duì)象,發(fā)送消息,響應(yīng)消息,這就構(gòu)成了OOP計(jì)算模型的根本。

OOP強(qiáng)調(diào)封裝、繼承、多態(tài)、抽象

“壞設(shè)計(jì)”的定義


?改變起來(lái)很難,因?yàn)槊糠N變化都會(huì)影響系統(tǒng)的太多其他部分。(Rigidity剛性、僵硬)。

?當(dāng)你作了一個(gè)變動(dòng)時(shí),系統(tǒng)中意想不到的部分會(huì)出錯(cuò)。(Fragility、易碎性)

?它難以在另一個(gè)應(yīng)用程序中復(fù)用,由于它不能脫離當(dāng)前應(yīng)用。(Immobility、固定、無(wú)移植性)

是什么導(dǎo)致設(shè)計(jì)剛性(rigid)、脆弱(fragile)和不易移植(immobile)的呢?它(原因)就是該設(shè)計(jì)中模塊的相互依賴(interdependence)。這個(gè)設(shè)計(jì)就是剛性的,如果它不能容易地被改變,這樣的剛性是因?yàn)檫@一事實(shí)——對(duì)于嚴(yán)重相互依賴的軟件,單個(gè)變化引起了依賴模塊中的級(jí)聯(lián)變化(a cascade of changes、連鎖反應(yīng))。一旦級(jí)聯(lián)變化的范圍不能被設(shè)計(jì)者或者維護(hù)者預(yù)先知道,變化的影響就不能被估計(jì)。這導(dǎo)致變化的開(kāi)銷不可能被預(yù)言。管理層面對(duì)如此不可預(yù)測(cè)性,變得不愿意批準(zhǔn)變動(dòng)。因此,設(shè)計(jì)就官方上(正式、officially)地成為剛性。

脆弱性(易碎性)是一種單個(gè)變化發(fā)生時(shí),程序在很多地方中斷的趨勢(shì)。經(jīng)常的,新問(wèn)題出現(xiàn)在與被改變的領(lǐng)域沒(méi)有概念上的關(guān)系的地方。這樣的易碎性極大降低了設(shè)計(jì)的可信性和可維護(hù)性。用戶和管理者不能預(yù)言他們的產(chǎn)品的質(zhì)量。應(yīng)用的某個(gè)部分的簡(jiǎn)單變化導(dǎo)致在看起來(lái)完全無(wú)關(guān)的其他部分出現(xiàn)失敗。

解決那些問(wèn)題導(dǎo)致甚至更多的問(wèn)題,而維護(hù)工作開(kāi)始(變得)像一條狗追趕它尾巴。

設(shè)計(jì)是不易移植的,是說(shuō)設(shè)計(jì)中想要的部分高度地依賴于不太想要的細(xì)節(jié)。一些設(shè)計(jì)者被分配的任務(wù)是研究(調(diào)查)設(shè)計(jì),看看它能否在不同的應(yīng)用中復(fù)用,他們會(huì)驚嘆于該設(shè)計(jì)能夠那么好的復(fù)用于新的應(yīng)用中然而,如果一個(gè)設(shè)計(jì)是高度互相依賴的,他們就會(huì)非常的苦惱,把該設(shè)計(jì)中想要的部分與該設(shè)計(jì)中不想要的部分分開(kāi),有大量的工作必須做。多數(shù)情況下,這樣的設(shè)計(jì)是不被復(fù)用的,因?yàn)榉蛛x的費(fèi)用被認(rèn)為要高于重新設(shè)計(jì)的費(fèi)用。

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


?軟件開(kāi)發(fā)唯一永恒真理“需求永遠(yuǎn)在改變”

?面向軟件設(shè)計(jì)OOD目的,解決軟件系統(tǒng)的可維護(hù)性、可擴(kuò)展性、可復(fù)用性

軟件開(kāi)發(fā)唯一真理“需求永遠(yuǎn)在改變”,因?yàn)檐浖鉀Q的是現(xiàn)實(shí)生活中的業(yè)務(wù)問(wèn)題,而業(yè)務(wù)流程總是在不停的變化。

OOP是軟件開(kāi)發(fā)的基礎(chǔ),OOD是軟件開(kāi)發(fā)的整體規(guī)劃,在設(shè)計(jì)軟件時(shí),代碼要求面相對(duì)象、模塊化、可復(fù)用、能以最小代價(jià)滿足變化、不用改變現(xiàn)有代碼滿足擴(kuò)展

設(shè)計(jì)模式就是在這個(gè)過(guò)程中不斷總結(jié)出來(lái)的優(yōu)秀設(shè)計(jì)方案,在此基礎(chǔ)之上再次提煉才形成了我們今天要講的設(shè)計(jì)原則

OOD最基本的設(shè)計(jì)原則


?單一職責(zé)原則SRP? Single Responsibility Principle

?開(kāi)閉原則OCP? Opened Closed Principle

?里氏替換原則LSP? Liscov Substitution Principle

?接口隔離原則ISP? Interface Segregation Principle

?依賴倒置原則DIP? Dependency Inversion Principle

單一職責(zé)原則



并不是因?yàn)槟隳?,你就?yīng)該做

”并不是因?yàn)槟隳?,你就?yīng)該做”。為什么?因?yàn)殚L(zhǎng)遠(yuǎn)來(lái)看它會(huì)帶來(lái)很多管理問(wèn)題。

從面向?qū)ο蠼嵌冉忉尀椋骸币痤愖兓囊蛩赜肋h(yuǎn)不要多于一個(gè)?!?/p>

或者說(shuō)”一個(gè)類有且只有一個(gè)職責(zé)”。這個(gè)原則是說(shuō),如果你的類有多于一個(gè)原因會(huì)導(dǎo)致它變化(或者多于一個(gè)職責(zé)),你需要依據(jù)它們的職責(zé)把這個(gè)類拆分為多個(gè)類。

●每個(gè)職責(zé)是軸向變化的;

●如果類包含多個(gè)職責(zé),代碼會(huì)變得耦合;

?單一職責(zé)原則是最簡(jiǎn)單的面向?qū)ο笤O(shè)計(jì)原則,它用于控制類的粒度大小。單一職責(zé)原則定義如下:?jiǎn)我宦氊?zé)原則(Single Responsibility Principle, SRP):一個(gè)類只負(fù)責(zé)一個(gè)功能領(lǐng)域中的相應(yīng)職責(zé),或者可以定義為:就一個(gè)類而言,應(yīng)該只有一個(gè)引起它變化的原因。

?單一職責(zé)原則是實(shí)現(xiàn)高內(nèi)聚、低耦合的指導(dǎo)方針,它是最簡(jiǎn)單但又最難運(yùn)用的原則

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

某軟件公司開(kāi)發(fā)出CRM系統(tǒng),其中客戶信息圖形統(tǒng)計(jì)模塊設(shè)計(jì)方案如下圖

不符合單一職責(zé)原則的類圖

在圖中,CustomerDataChart類中的方法說(shuō)明如下:getConnection()方法用于連接數(shù)據(jù)庫(kù),findCustomers()用于查詢所有的客戶信息,createChart()用于創(chuàng)建圖表,displayChart()用于顯示圖表。

CustomerDataChart類承擔(dān)了太多的職責(zé),既包含與數(shù)據(jù)庫(kù)相關(guān)的方法,又包含與圖表生成和顯示相關(guān)的方法。如果在其他類中也需要連接數(shù)據(jù)庫(kù)或者使用findCustomers()方法查詢客戶信息,則難以實(shí)現(xiàn)代碼的重用。無(wú)論是修改數(shù)據(jù)庫(kù)連接方式還是修改圖表顯示方式都需要修改該類,它不止一個(gè)引起它變化的原因,違背了單一職責(zé)原則。

根據(jù)單一職責(zé)原則修改后

符合單一職責(zé)原則的類圖

(1) DBUtil:負(fù)責(zé)連接數(shù)據(jù)庫(kù),包含數(shù)據(jù)庫(kù)連接方法getConnection();

(2) CustomerDAO:負(fù)責(zé)操作數(shù)據(jù)庫(kù)中的Customer表,包含對(duì)Customer表的增刪改查等方法,如findCustomers();

(3) CustomerDataChart:負(fù)責(zé)圖表的生成和顯示,包含方法createChart()和displayChart()。

符合開(kāi)閉原則的設(shè)計(jì)


?開(kāi)閉原則是面向?qū)ο蟮目蓮?fù)用設(shè)計(jì)的第一塊基石,它是最重要的面向?qū)ο笤O(shè)計(jì)原則。其定義如下:開(kāi)閉原則(Open-Closed Principle, OCP):一個(gè)軟件實(shí)體應(yīng)當(dāng)對(duì)擴(kuò)展開(kāi)放,對(duì)修改關(guān)閉。即軟件實(shí)體應(yīng)盡量在不修改原有代碼的情況下進(jìn)行擴(kuò)展。

?為了滿足開(kāi)閉原則,需要對(duì)系統(tǒng)進(jìn)行抽象化設(shè)計(jì),抽象化是開(kāi)閉原則的關(guān)鍵。

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

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

某軟件公司開(kāi)發(fā)的CRM系統(tǒng)可以顯示各種類型的圖表,如餅狀圖和柱狀圖等

不符合開(kāi)閉原則的類圖

在該代碼中,如果需要增加一個(gè)新的圖表類,如折線圖LineChart,則需要修改ChartDisplay類的display()方法的源代碼,增加新的判斷邏輯,違反了開(kāi)閉原則。

在本實(shí)例中,由于在ChartDisplay類的display()方法中針對(duì)每一個(gè)圖表類編程,因此增加新的圖表類不得不修改源代碼??梢酝ㄟ^(guò)抽象化的方式對(duì)系統(tǒng)進(jìn)行重構(gòu),使之增加新的圖表類時(shí)無(wú)須修改源代碼,滿足開(kāi)閉原則

(1)?增加一個(gè)抽象圖表類AbstractChart,將各種具體圖表類作為其子類;

(2)? ChartDisplay類針對(duì)抽象圖表類進(jìn)行編程,由客戶端來(lái)決定使用哪種具體圖表。

符合開(kāi)閉原則的類圖

在圖中,我們引入了抽象圖表類AbstractChart,且ChartDisplay針對(duì)抽象圖表類進(jìn)行編程,并通過(guò)setChart()方法由客戶端來(lái)設(shè)置實(shí)例化的具體圖表對(duì)象,在ChartDisplay的display()方法中調(diào)用chart對(duì)象的display()方法顯示圖表。如果需要增加一種新的圖表,如折線圖LineChart,只需要將LineChart也作為AbstractChart的子類,在客戶端向ChartDisplay中注入一個(gè)LineChart對(duì)象即可,無(wú)須修改現(xiàn)有類庫(kù)的源代碼。?????

里氏代換原則


?里氏代換原則由2008年圖靈獎(jiǎng)得主、美國(guó)第一位計(jì)算機(jī)科學(xué)女博士Barbara Liskov教授和卡內(nèi)基·梅隆大學(xué)Jeannette

Wing教授于1994年提出。其表述如下:里氏代換原則(Liskov Substitution Principle, LSP):所有引用基類(父類)的地方必須能透明地使用其子類的對(duì)象。

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

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

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

(1)子類的所有方法必須在父類中聲明,或子類必須實(shí)現(xiàn)父類中聲明的所有方法。根據(jù)里氏代換原則,為了保證系統(tǒng)的擴(kuò)展性,在程序中通常使用父類來(lái)進(jìn)行定義,如果一個(gè)方法只存在子類中,在父類中不提供相應(yīng)的聲明,則無(wú)法在以父類定義的對(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í)無(wú)須修改原有子類的代碼,增加新的功能可以通過(guò)增加一個(gè)新的子類來(lái)實(shí)現(xiàn)。里氏代換原則是開(kāi)閉原則的具體實(shí)現(xiàn)手段之一。

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

某軟件公司開(kāi)發(fā)的CRM系統(tǒng)中,系統(tǒng)需要提供一個(gè)發(fā)送Email的功能,原始設(shè)計(jì)方案如圖1所示:

不符合里氏替換原則的設(shè)計(jì)

在對(duì)系統(tǒng)進(jìn)行進(jìn)一步分析后發(fā)現(xiàn),無(wú)論是普通客戶還是VIP客戶,發(fā)送郵件的過(guò)程都是相同的,也就是說(shuō)兩個(gè)send()方法中的代碼重復(fù),而且在本系統(tǒng)中還將增加新類型的客戶。為了讓系統(tǒng)具有更好的擴(kuò)展性,同時(shí)減少代碼重復(fù),使用里氏代換原則對(duì)其進(jìn)行重構(gòu)。


符合里氏替換原則的設(shè)計(jì)

在本實(shí)例中,可以考慮增加一個(gè)新的抽象客戶類Customer,而將CommonCustomer和VIPCustomer類作為其子類,郵件發(fā)送類EmailSender類針對(duì)抽象客戶類Customer編程,根據(jù)里氏代換原則,能夠接受基類對(duì)象的地方必然能夠接受子類對(duì)象,因此將EmailSender中的send()方法的參數(shù)類型改為Customer,如果需要增加新類型的客戶,只需將其作為Customer類的子類即可。重構(gòu)后的結(jié)構(gòu)如圖2所示:

里氏代換原則是實(shí)現(xiàn)開(kāi)閉原則的重要方式之一。在本實(shí)例中,在傳遞參數(shù)時(shí)使用基類對(duì)象,除此以外,在定義成員變量、定義局部變量、確定方法返回類型時(shí)都可使用里氏代換原則。針對(duì)基類編程,在程序運(yùn)行時(shí)再確定具體子類。

依賴倒轉(zhuǎn)原則

?如果說(shuō)開(kāi)閉原則是面向?qū)ο笤O(shè)計(jì)的目標(biāo)的話,那么依賴倒轉(zhuǎn)原則就是面向?qū)ο笤O(shè)計(jì)的主要實(shí)現(xiàn)機(jī)制之一,它是系統(tǒng)抽象化的具體實(shí)現(xiàn)。依賴倒轉(zhuǎn)原則(Dependency Inversion? Principle,

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

?在實(shí)現(xiàn)依賴倒轉(zhuǎn)原則時(shí),我們需要針對(duì)抽象層編程,而將具體類的對(duì)象通過(guò)依賴注入(DependencyInjection, DI)的方式注入到其他對(duì)象中。常用的注入方式有三種,分別是:構(gòu)造注入,設(shè)值注入(Setter注入)和接口注入。

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

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

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

講了這么多,估計(jì)大家對(duì)“倒置”這個(gè)詞還是有點(diǎn)不理解,那到底什么是“倒置”呢?我們先說(shuō)“正置”是什么意思,依賴正置就是類間的依賴是實(shí)實(shí)在在的實(shí)現(xiàn)類間的依賴,也就是面向?qū)崿F(xiàn)編程,這也是正常人的思維方式,我要開(kāi)奔馳車就依賴奔馳車,我要使用筆記本電腦就直接依賴筆記本電腦,而編寫程序需要的是對(duì)現(xiàn)實(shí)世界的事物進(jìn)行抽象,抽象的結(jié)果就是有了抽象類和接口,然后我們根據(jù)系統(tǒng)設(shè)計(jì)的需要產(chǎn)生了抽象間的依賴,代替了人們傳統(tǒng)思維中的事物間的依賴,“倒置”就是從這里產(chǎn)生的。

軟件公司開(kāi)發(fā)人員在開(kāi)發(fā)某CRM系統(tǒng)時(shí)發(fā)現(xiàn):該系統(tǒng)經(jīng)常需要將文件中的客戶信息轉(zhuǎn)存到數(shù)據(jù)庫(kù)中

不符合依賴倒置原則的設(shè)計(jì)

在編碼實(shí)現(xiàn)圖1所示結(jié)構(gòu)時(shí),Sunny軟件公司開(kāi)發(fā)人員發(fā)現(xiàn)該設(shè)計(jì)方案存在一個(gè)非常嚴(yán)重的問(wèn)題,由于每次轉(zhuǎn)換數(shù)據(jù)時(shí)數(shù)據(jù)來(lái)源不一定相同,因此需要更換數(shù)據(jù)轉(zhuǎn)換類,如有時(shí)候需要將TXTDataConvertor改為ExcelDataConvertor,此時(shí),需要修改CustomerDAO的源代碼,而且在引入并使用新的數(shù)據(jù)轉(zhuǎn)換類時(shí)也不得不修改CustomerDAO的源代碼,系統(tǒng)擴(kuò)展性較差,違反了開(kāi)閉原則,現(xiàn)需要對(duì)該方案進(jìn)行重構(gòu)。

符合依賴倒置原則的設(shè)計(jì)

由于CustomerDAO針對(duì)具體數(shù)據(jù)轉(zhuǎn)換類編程,因此在增加新的數(shù)據(jù)轉(zhuǎn)換類或者更換數(shù)據(jù)轉(zhuǎn)換類時(shí)都不得不修改CustomerDAO的源代碼。我們可以通過(guò)引入抽象數(shù)據(jù)轉(zhuǎn)換類解決該問(wèn)題,在引入抽象數(shù)據(jù)轉(zhuǎn)換類DataConvertor之后,CustomerDAO針對(duì)抽象類DataConvertor編程,而將具體數(shù)據(jù)轉(zhuǎn)換類名存儲(chǔ)在配置文件中,符合依賴倒轉(zhuǎn)原則。根據(jù)里氏代換原則,程序運(yùn)行時(shí),具體數(shù)據(jù)轉(zhuǎn)換類對(duì)象將替換DataConvertor類型的對(duì)象,程序不會(huì)出現(xiàn)任何問(wèn)題。更換具體數(shù)據(jù)轉(zhuǎn)換類時(shí)無(wú)須修改源代碼,只需要修改配置文件;如果需要增加新的具體數(shù)據(jù)轉(zhuǎn)換類,只要將新增數(shù)據(jù)轉(zhuǎn)換類作為DataConvertor的子類并修改配置文件即可,原有代碼無(wú)須做任何修改,滿足開(kāi)閉原則。

在上述重構(gòu)過(guò)程中,我們使用了開(kāi)閉原則、里氏代換原則和依賴倒轉(zhuǎn)原則,在大多數(shù)情況下,這三個(gè)設(shè)計(jì)原則會(huì)同時(shí)出現(xiàn),開(kāi)閉原則是目標(biāo),里氏代換原則是基礎(chǔ),依賴倒轉(zhuǎn)原則是手段,它們相輔相成,相互補(bǔ)充,目標(biāo)一致,只是分析問(wèn)題時(shí)所站角度不同而已。

接口隔離原則


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

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

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

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

軟件公司開(kāi)發(fā)人員針對(duì)某CRM系統(tǒng)的客戶數(shù)據(jù)顯示模塊設(shè)計(jì)了如圖1所示接口

不符合接口隔離原則的設(shè)計(jì)

其中方法dataRead()用于從文件中讀取數(shù)據(jù),方法transformToXML()用于將數(shù)據(jù)轉(zhuǎn)換成XML格式,方法createChart()用于創(chuàng)建圖表,方法displayChart()用于顯示圖表,方法createReport()用于創(chuàng)建文字報(bào)表,方法displayReport()用于顯示文字報(bào)表。

在實(shí)際使用過(guò)程中發(fā)現(xiàn)該接口很不靈活,例如如果一個(gè)具體的數(shù)據(jù)顯示類無(wú)須進(jìn)行數(shù)據(jù)轉(zhuǎn)換(源文件本身就是XML格式),但由于實(shí)現(xiàn)了該接口,將不得不實(shí)現(xiàn)其中聲明的transformToXML()方法(至少需要提供一個(gè)空實(shí)現(xiàn));如果需要?jiǎng)?chuàng)建和顯示圖表,除了需實(shí)現(xiàn)與圖表相關(guān)的方法外,還需要實(shí)現(xiàn)創(chuàng)建和顯示文字報(bào)表的方法,否則程序編譯時(shí)將報(bào)錯(cuò)。

使用接口隔離原則后的設(shè)計(jì)

在圖中,由于在接口CustomerDataDisplay中定義了太多方法,即該接口承擔(dān)了太多職責(zé),一方面導(dǎo)致該接口的實(shí)現(xiàn)類很龐大,在不同的實(shí)現(xiàn)類中都不得不實(shí)現(xiàn)接口中定義的所有方法,靈活性較差,如果出現(xiàn)大量的空方法,將導(dǎo)致系統(tǒng)中產(chǎn)生大量的無(wú)用代碼,影響代碼質(zhì)量;另一方面由于客戶端針對(duì)大接口編程,將在一定程序上破壞程序的封裝性,客戶端看到了不應(yīng)該看到的方法,沒(méi)有為客戶端定制接口。因此需要將該接口按照接口隔離原則和單一職責(zé)原則進(jìn)行重構(gòu),將其中的一些方法封裝在不同的小接口中,確保每一個(gè)接口使用起來(lái)都較為方便,并都承擔(dān)某一單一角色,每個(gè)接口中只包含一個(gè)客戶端(如模塊或類)所需的方法即可。

在使用接口隔離原則時(shí),我們需要注意控制接口的粒度,接口不能太小,如果太小會(huì)導(dǎo)致系統(tǒng)中接口泛濫,不利于維護(hù);接口也不能太大,太大的接口將違背接口隔離原則,靈活性較差,使用起來(lái)很不方便。一般而言,接口中僅包含為某一類用戶定制的方法即可,不應(yīng)該強(qiáng)迫客戶依賴于那些它們不用的方法。

其他設(shè)計(jì)原則

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

?合成/聚合復(fù)用原則(Composite/Aggregate Reuse Principle,CARP)

?You Ain’t Gonna Need It (YAGNI)

迪米特法則還有幾種定義形式,包括:不要和“陌生人”說(shuō)話、只與你的直接朋友通信等,在迪米特法則中,對(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ì)象的“朋友”,否則就是“陌生人”。在應(yīng)用迪米特法則時(shí),一個(gè)對(duì)象只能與直接朋友發(fā)生交互,不要與“陌生人”發(fā)生直接交互,這樣做可以降低系統(tǒng)的耦合度,一個(gè)對(duì)象的改變不會(huì)給太多其他對(duì)象帶來(lái)影響。

迪米特法則要求我們?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è)方法的話,可以通過(guò)第三者轉(zhuǎn)發(fā)這個(gè)調(diào)用。簡(jiǎn)言之,就是通過(guò)引入一個(gè)合理的第三者來(lái)降低現(xiàn)有對(duì)象之間的耦合度。

在將迪米特法則運(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ù)的訪問(wèn)權(quán)限;在類的設(shè)計(jì)上,只要有可能,一個(gè)類型應(yīng)當(dāng)設(shè)計(jì)成不變類;在對(duì)其他類的引用上,一個(gè)對(duì)象對(duì)其他對(duì)象的引用應(yīng)當(dāng)降到最低。

在一個(gè)新的對(duì)象里面使用一些已有的對(duì)象,使之成為新對(duì)象的一部分;新的對(duì)象通過(guò)這些向?qū)ο蟮奈蛇_(dá)到復(fù)用已有功能的目的.這個(gè)設(shè)計(jì)原則有另一個(gè)簡(jiǎn)短的表述:要盡量使用合成/聚合,盡量不要使用繼承.

?著作權(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)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • 我們?cè)趹?yīng)用開(kāi)發(fā)中,一般要求盡量做到可維護(hù)性和可復(fù)用性 應(yīng)用程序的復(fù)用可以提高應(yīng)用程序的開(kāi)發(fā)效率和質(zhì)量,節(jié)約開(kāi)發(fā)成本...
    Yochi閱讀 596評(píng)論 0 0
  • Unity腳本編程 眾所周知,unity的編程屬于腳本化,腳本沒(méi)有一個(gè)具體的概念跟架構(gòu), 導(dǎo)致在項(xiàng)目過(guò)程中,經(jīng)常出...
    半閑書(shū)屋半閑人閱讀 3,244評(píng)論 1 28
  • 設(shè)計(jì)原則就是在設(shè)計(jì)模式背后更為深層的、更具有普遍性的、共同的思想原則,是提高軟件系統(tǒng)的可維護(hù)性和可復(fù)用性的指導(dǎo)原則...
    蘇先生Tongson閱讀 1,277評(píng)論 0 0
  • 七大設(shè)計(jì)原則: 開(kāi)閉原則單一職責(zé)原則里氏替換原則依賴倒置原則接口隔離原則最少知識(shí)原則(迪米特法則)少用繼承多用組合...
    _涼笙閱讀 1,200評(píng)論 0 0
  • 今天做了件很不好的事情,很是后悔自責(zé)。 下午娃要求買零食我不同意,也不同意老公給她買,她便開(kāi)始大哭不止,我很生...
    木小槿閱讀 269評(píng)論 2 1

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