接口與接口設(shè)計(jì)原則_第1頁(yè)
接口與接口設(shè)計(jì)原則_第2頁(yè)
接口與接口設(shè)計(jì)原則_第3頁(yè)
接口與接口設(shè)計(jì)原則_第4頁(yè)
接口與接口設(shè)計(jì)原則_第5頁(yè)
已閱讀5頁(yè),還剩56頁(yè)未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說(shuō)明:本文檔由用戶(hù)提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡(jiǎn)介

接口與接口設(shè)計(jì)原則一.11種設(shè)計(jì)原則.單一職責(zé)原則一SingleResponsibilityPrinciple(SRP)就一個(gè)類(lèi)而言,應(yīng)該僅有一個(gè)引起它變化的原因.職責(zé)即為“變化的原因〃.2。開(kāi)放-封閉原則一OpenClosePrinciple(OCP)軟件實(shí)體(類(lèi)、模塊、函數(shù)等)應(yīng)該是可以擴(kuò)展的,但是不可修改.對(duì)于擴(kuò)展是開(kāi)放的,對(duì)于更改是封閉的。關(guān)鍵是抽象。將一個(gè)功能的通用部分和實(shí)現(xiàn)細(xì)節(jié)部分清晰的分離開(kāi)來(lái)。開(kāi)發(fā)人員應(yīng)該僅僅對(duì)程序中呈現(xiàn)出頻繁變化的那些部分作出抽象.拒絕不成熟的抽象和抽象本身一樣重要)3.里氏替換原則-LiskovSubstitutionPrinciple(LSP)子類(lèi)型(subclass)必須能夠替換掉它們的基類(lèi)型(superclass)。4。依賴(lài)倒置原則(IoCP)或依賴(lài)注入原則-DependenceInversionPrinciple(DIP)抽象不應(yīng)該依賴(lài)于細(xì)節(jié).細(xì)節(jié)應(yīng)該依賴(lài)于抽象.Hollywood原則:”Don'tcallus,we'llcallyou".程序中所有的依賴(lài)關(guān)系都應(yīng)該終止于抽象類(lèi)和接口。針對(duì)接口而非實(shí)現(xiàn)編程.任何變量都不應(yīng)該持有一個(gè)指向具體類(lèi)的指針或引用。任何類(lèi)都不應(yīng)該從具體類(lèi)派生。任何方法都不應(yīng)該覆寫(xiě)他的任何基類(lèi)中的已經(jīng)實(shí)現(xiàn)了的方法。5.接口隔離原則(ISP)不應(yīng)該強(qiáng)迫客戶(hù)依賴(lài)于它們不用的方法。接口屬于客戶(hù),不屬于它所在的類(lèi)層次結(jié)構(gòu).多個(gè)面向特定用戶(hù)的接口勝于一個(gè)通用接口。6。重用發(fā)布等價(jià)原則(REP)重用的粒度就是發(fā)布的粒度。7。共同封閉原則(CCP)包(類(lèi)庫(kù)、DLL)中的所有類(lèi)對(duì)于同一類(lèi)性質(zhì)的變化應(yīng)該是共同封閉的。一個(gè)變化若對(duì)一個(gè)包產(chǎn)生影響,則將對(duì)該包中的所有類(lèi)產(chǎn)生影響,而對(duì)于其他的包不造成任何影響..共同重用原則(CRP)一個(gè)包(類(lèi)庫(kù)、DLL)中的所有類(lèi)應(yīng)該是共同重用的。如果重用了包(類(lèi)庫(kù)、DLL)中的一個(gè)類(lèi),那么就要重用包(類(lèi)庫(kù)、DLL)中的所有類(lèi)。(相互之間沒(méi)有緊密聯(lián)系的類(lèi)不應(yīng)該在同一個(gè)包(類(lèi)庫(kù)、DLL)中。)包(類(lèi)庫(kù)、DLL)耦合原則.無(wú)環(huán)依賴(lài)原則(ADP)在包的依賴(lài)關(guān)系圖中不允許存在環(huán)。10。穩(wěn)定依賴(lài)原則(SDP)朝著穩(wěn)定的方向進(jìn)行依賴(lài).應(yīng)該把封裝系統(tǒng)高層設(shè)計(jì)的軟件(比如抽象類(lèi))放進(jìn)穩(wěn)定的包中,不穩(wěn)定的包中應(yīng)該只包含那些很可能會(huì)改變的軟件(比如具體類(lèi))。11。穩(wěn)定抽象原則(SAP)包的抽象程度應(yīng)該和其穩(wěn)定程度一致。一個(gè)穩(wěn)定的包應(yīng)該也是抽象的,一個(gè)不穩(wěn)定的包應(yīng)該是抽象的.其它擴(kuò)展原則.BBP(BlackBoxPrinciple)黑盒原則多用類(lèi)的聚合,少用類(lèi)的繼承。13.DAP(DefaultAbstractionPrinciple)缺省抽象原則在接口和實(shí)現(xiàn)接口的類(lèi)之間引入一個(gè)抽象類(lèi),這個(gè)類(lèi)實(shí)現(xiàn)了接口的大部分操作.14。IDP(InterfaceDesignPrinciple)接口設(shè)計(jì)原則規(guī)劃一個(gè)接口而不是實(shí)現(xiàn)一個(gè)接口。15.DCSP(Don'tConcreteSupperclassPrinciple)不要構(gòu)造具體的超類(lèi)原則,避免維護(hù)具體的超類(lèi)。16。迪米特法則一個(gè)類(lèi)只依賴(lài)其觸手可得的類(lèi)。二.類(lèi)的設(shè)計(jì)原則1。開(kāi)閉原則Softwareentities(classes,modules,function,etc.)shouldbeopenforextension,butclosedformodification。軟件實(shí)體(模塊,類(lèi),方法等)應(yīng)該對(duì)擴(kuò)展開(kāi)放,對(duì)修改關(guān)閉.開(kāi)閉原則(OCP:Open—ClosedPrinciple)是指在進(jìn)行面向?qū)ο笤O(shè)計(jì)(OOD:ObjectOrientedDesign)中,設(shè)計(jì)類(lèi)或其他程序單位時(shí),應(yīng)該遵循:-對(duì)擴(kuò)展開(kāi)放(open)—對(duì)修改關(guān)閉(closed)的設(shè)計(jì)原則.開(kāi)閉原則是判斷面向?qū)ο笤O(shè)計(jì)是否正確的最基本的原理之一。根據(jù)開(kāi)閉原則,在設(shè)計(jì)一個(gè)軟件系統(tǒng)模塊(類(lèi),方法)的時(shí)候,應(yīng)該可以在不修改原有的模塊(修改關(guān)閉)的基礎(chǔ)上,能擴(kuò)展其功能(擴(kuò)展開(kāi)放)。一擴(kuò)展開(kāi)放:某模塊的功能是可擴(kuò)展的,則該模塊是擴(kuò)展開(kāi)放的.軟件系統(tǒng)的功能上的可擴(kuò)展性要求模塊是擴(kuò)展開(kāi)放的。修改關(guān)閉:某模塊被其他模塊調(diào)用,如果該模塊的源代碼不允許修改,則該模塊修改關(guān)閉的。軟件系統(tǒng)的功能上的穩(wěn)定性,持續(xù)性要求是修改關(guān)閉的.這也是系統(tǒng)設(shè)計(jì)需要遵循開(kāi)閉原則的原因:1)穩(wěn)定性。開(kāi)閉原則要求擴(kuò)展功能不修改原來(lái)的代碼,這可以讓軟件系統(tǒng)在變化中保持穩(wěn)定。2)擴(kuò)展性。開(kāi)閉原則要求對(duì)擴(kuò)展開(kāi)放,通過(guò)擴(kuò)展提供新的或改變?cè)械墓δ?,讓軟件系統(tǒng)具有靈活的可擴(kuò)展性。遵循開(kāi)閉原則的系統(tǒng)設(shè)計(jì),可以讓軟件系統(tǒng)可復(fù)用,并且易于維護(hù)。開(kāi)閉原則的實(shí)現(xiàn)方法為了滿(mǎn)足開(kāi)閉原則的對(duì)修改關(guān)閉(closedformodification)原則以及擴(kuò)展開(kāi)放(openforextension)原則,應(yīng)該對(duì)軟件系統(tǒng)中的不變的部分加以抽象,在面向?qū)ο蟮脑O(shè)計(jì)中,可以把這些不變的部分加以抽象成不變的接口,這些不變的接口可以應(yīng)對(duì)未來(lái)的擴(kuò)展;-接口的最小功能設(shè)計(jì)原則。根據(jù)這個(gè)原則,原有的接口要么可以應(yīng)對(duì)未來(lái)的擴(kuò)展;不足的部分可以通過(guò)定義新的接口來(lái)實(shí)現(xiàn);模塊之間的調(diào)用通過(guò)抽象接口進(jìn)行,這樣即使實(shí)現(xiàn)層發(fā)生變化,也無(wú)需修改調(diào)用方的代碼。接口可以被復(fù)用,但接口的實(shí)現(xiàn)卻不一定能被復(fù)用.接口是穩(wěn)定的,關(guān)閉的,但接口的實(shí)現(xiàn)是可變的,開(kāi)放的??梢酝ㄟ^(guò)對(duì)接口的不同實(shí)現(xiàn)以及類(lèi)的繼承行為等為系統(tǒng)增加新的或改變系統(tǒng)原來(lái)的功能,實(shí)現(xiàn)軟件系統(tǒng)的柔軟擴(kuò)展。簡(jiǎn)單地說(shuō),軟件系統(tǒng)是否有良好的接口(抽象)設(shè)計(jì)是判斷軟件系統(tǒng)是否滿(mǎn)足開(kāi)閉原則的一種重要的判斷基準(zhǔn)?,F(xiàn)在多把開(kāi)閉原則等同于面向接口的軟件設(shè)計(jì)。開(kāi)閉原則的相對(duì)性軟件系統(tǒng)的構(gòu)建是一個(gè)需要不斷重構(gòu)的過(guò)程,在這個(gè)過(guò)程中,模塊的功能抽象,模塊與模塊間的關(guān)系,都不會(huì)從一開(kāi)始就非常清晰明了,所以構(gòu)建100%滿(mǎn)足開(kāi)閉原則的軟件系統(tǒng)是相當(dāng)困難的,這就是開(kāi)閉原則的相對(duì)性。但在設(shè)計(jì)過(guò)程中,通過(guò)對(duì)模塊功能的抽象(接口定義),模塊之間的關(guān)系的抽象(通過(guò)接口調(diào)用),抽象與實(shí)現(xiàn)的分離(面向接口的程序設(shè)計(jì))等,可以盡量接近滿(mǎn)足開(kāi)閉原則。2。單一職責(zé)原則前百RobertC。Martin氏為我們總結(jié)了在面向?qū)ο蟮脑O(shè)計(jì)(00D)中應(yīng)該遵循的原則,這些原則被稱(chēng)為“PrinciplesofOOD〃,關(guān)于“Principlesof00D”的相關(guān)文章可以從ObjectMenter得到。本文介紹“Principlesof00D〃中的單一職責(zé)原則:SingleResponsibilityPrinciple(SRP).可以從這里查看SingleResponsibilityPrinciple(SRP)的原文.概要Thereshouldneverbemorethanonereasonforaclasstochange.永遠(yuǎn)不要讓一個(gè)類(lèi)存在多個(gè)改變的理由。換句話(huà)說(shuō),如果一個(gè)類(lèi)需要改變,改變它的理由永遠(yuǎn)只有一個(gè)。如果存在多個(gè)改變它的理由,就需要重新設(shè)計(jì)該類(lèi)。SRP(SingleResponsibilityPrinciple)原則的核心含意是:只能讓一個(gè)類(lèi)有且僅有一個(gè)職責(zé)。這也是單一職責(zé)原則的命名含義.為什么一個(gè)類(lèi)不能有多于一個(gè)以上的職責(zé)呢?如果一個(gè)類(lèi)具有一個(gè)以上的職責(zé),那么就會(huì)有多個(gè)不同的原因引起該類(lèi)變化,而這種變化將影響到該類(lèi)不同職責(zé)的使用者(不同用戶(hù)):1,一方面,如果一個(gè)職責(zé)使用了外部類(lèi)庫(kù),則使用另外一個(gè)職責(zé)的用戶(hù)卻也不得不包含這個(gè)未被使用的外部類(lèi)庫(kù)。2,另一方面,某個(gè)用戶(hù)由于某個(gè)原因需要修改其中一個(gè)職責(zé),另外一個(gè)職責(zé)的用戶(hù)也將受到影響,他將不得不重新編譯和配置.這違反了設(shè)計(jì)的開(kāi)閉原則,也不是我們所期望的。職責(zé)的劃分既然一個(gè)類(lèi)不能有多個(gè)職責(zé),那么怎么劃分職責(zé)呢?Robert.CMartin給出了一個(gè)著名的定義:所謂一個(gè)類(lèi)的一個(gè)職責(zé)是指引起該類(lèi)變化的一個(gè)原因。Ifyoucanthinkofmorethanonemotiveforchangingaclass,thenthatclasshasmorethanoneresponsibility。如果你能想到一個(gè)類(lèi)存在多個(gè)使其改變的原因,那么這個(gè)類(lèi)就存在多個(gè)職責(zé).SingleResponsibilityPrinciple(SRP)的原文里舉了一個(gè)Modem的例子來(lái)說(shuō)明怎么樣進(jìn)行職責(zé)的劃分,這里我們也沿用這個(gè)例子來(lái)說(shuō)明一下:SRP違反例:publicinterfaceModem{〃撥號(hào)publicvoiddial(Stringpno);〃撥號(hào)〃掛斷〃發(fā)送數(shù)據(jù)〃掛斷〃發(fā)送數(shù)據(jù)〃接收數(shù)據(jù))咋一看,這是一個(gè)沒(méi)有任何問(wèn)題的接口設(shè)計(jì)。但事實(shí)上,這個(gè)接口包含了2個(gè)職責(zé):第一個(gè)是連接管理(dial,hangup);另一個(gè)是數(shù)據(jù)通信(send,recv)。很多情況下,這2個(gè)職責(zé)沒(méi)有任何共通的部分,它們因?yàn)椴煌睦碛啥淖?,被不同部分的程序調(diào)用。所以它違反了SRP原則。下面的類(lèi)圖將它的2個(gè)不同職責(zé)分成2個(gè)不同的接口,這樣至少可以讓客戶(hù)端應(yīng)用程序使用具有單一職責(zé)的接口:GGonnection。diaKpno:String):vnidGGonnection。diaKpno:String):vnid呂hangup^);MoidDataOhannele?sendCch:charji:void?chsr接口定義0Mademlmpiementation 具體類(lèi)實(shí)現(xiàn)讓Modemimplementation實(shí)現(xiàn)這兩個(gè)接口。我們注意到,Modemimplementation又組合了2個(gè)職責(zé),這不是我們希望的,但有時(shí)這又是必須的。通常由于某些原因,迫使我們不得不綁定多個(gè)職責(zé)到一個(gè)類(lèi)中,但我們至少可以通過(guò)接口的分割來(lái)分離應(yīng)用程序關(guān)心的概念。事實(shí)上,這個(gè)例子一個(gè)更好的設(shè)計(jì)應(yīng)該是這樣的,如圖:QConnection。diaftpno:String):voidQConnection。diaftpno:String):voidADataChannel&send(-ph:charXvoidone已江上char接口定義?ModemConnectionO?ModemConnectionOModemDataChanneI具體類(lèi)實(shí)現(xiàn)小結(jié)SingleResponsibilityPrinciple(SRP)從職責(zé)(改變理由)的側(cè)面上為我們對(duì)類(lèi)(接口)的抽象的顆粒度建立了判斷基準(zhǔn):在為系統(tǒng)設(shè)計(jì)類(lèi)(接口)的時(shí)候應(yīng)該保證它們的單一職責(zé)性.3接口分隔原則前百RobertC。Martin氏為我們總結(jié)了在面向?qū)ο蟮脑O(shè)計(jì)(OOD)中應(yīng)該遵循的原則,這些原則被稱(chēng)為“PrinciplesofOOD”,關(guān)于“"PrinciplesofOOD”的相關(guān)文章可以從ObjectMenter得到。本文介紹“PrinciplesofOOD”中的接口分隔原則:InterfaceSegregationPrinciple(ISP)??梢詮倪@里查看InterfaceSegregationPrinciple(ISP)的原文.概要Clientsshouldnotbeforcedtodependuponinterfacesthattheydonotuse.不能強(qiáng)迫用戶(hù)去依賴(lài)那些他們不使用的接口。換句話(huà)說(shuō),使用多個(gè)專(zhuān)門(mén)的接口比使用單一的總接口總要好。它包含了2層意思:一接口的設(shè)計(jì)原則:接口的設(shè)計(jì)應(yīng)該遵循最小接口原則,不要把用戶(hù)不使用的方法塞進(jìn)同一個(gè)接口里。如果一個(gè)接口的方法沒(méi)有被使用到,則說(shuō)明該接口過(guò)胖,應(yīng)該將其分割成幾個(gè)功能專(zhuān)一的接口.一接口的依賴(lài)(繼承)原則:如果一個(gè)接口a依賴(lài)(繼承)另一個(gè)接口,則接口a相當(dāng)于繼承了接口6的方法,那么繼承了接口b后的接口a也應(yīng)該遵循上述原則:不應(yīng)該包含用戶(hù)不使用的方法。反之,則說(shuō)明接口a被b給污染了,應(yīng)該重新設(shè)計(jì)它們的關(guān)系。如果用戶(hù)被迫依賴(lài)他們不使用的接口,當(dāng)接口發(fā)生改變時(shí),他們也不得不跟著改變.換而言之,一個(gè)用戶(hù)依賴(lài)了未使用但被其他用戶(hù)使用的接口,當(dāng)其他用戶(hù)修改該接口時(shí),依賴(lài)該接口的所有用戶(hù)都將受到影響。這顯然違反了開(kāi)閉原則,也不是我們所期望的。下面我們舉例說(shuō)明怎么設(shè)計(jì)接口或類(lèi)之間的關(guān)系,使其不違反ISP原則。假如有一個(gè)Door,有l(wèi)ock,unlock功能,另外,可以在Door上安裝一個(gè)Alarm而使其具有報(bào)警功能.用戶(hù)可以選擇一般的Door,也可以選擇具有報(bào)警功能的Door。有以下幾種設(shè)計(jì)方法:ISP原則的違反例:方法一:在Door接口里定義所有的方法。圖:DoorolockO:void。unlockQ:voidQalarmO:uoid9CommonDoor@AlarmDoor?lo&k(^voidQunlock.0:void@alarmO:vqidoIockQjvoidQunlcic/):void&ahrmO:void但這樣一來(lái),依賴(lài)Door接口的CommonDoor卻不得不實(shí)現(xiàn)未使用的alarm()方法。違反了ISP原則。方法二:在Alarm接口定義alarm方法,在Door接口定義lock,unlock方法,Door接口繼承Alarm接口。

OAlarm電slarmO:voidIDoorolocki51;voidQunlock():void0alarmO-:vciid丁@GommonDoorolockC^fvoid@GommonDoorolockC^fvoid白u(yù)nlockG:voidoalarmt^'voidAlarmDoor-1c1dTuoid。unlockO;widRalarm?:void跟方法一一樣,依賴(lài)Door接口的CommonDoor卻不得不實(shí)現(xiàn)未使用的alarm()方法.違反了ISP原則.遵循ISP原則的例:方法三:通過(guò)多重繼承實(shí)現(xiàn)6AlarmQ6AlarmQalarmO:voiciA方案二Door杼Icick。?void由unlackO.void4K,?GoiriiYionDoorglockQ:void0unlockQ:voidAlarm電alarmO:voidI

IOAlarmDoor?IcjGK/'Wd&unlookO':void0alarmO:voidDooroIockftveiid◎unlovoid在Alarm接口定義alarm方法,在Door接口定義lock,unlock方法。接口之間無(wú)繼承關(guān)系。CommonDoor實(shí)現(xiàn)Door接口,AlarmDoor有2種實(shí)現(xiàn)方案:1),同時(shí)實(shí)現(xiàn)Door和Alarm接口。2),繼承CommonDoor,并實(shí)現(xiàn)Alarm接口。該方案是繼承方式的Adapter設(shè)計(jì)模式的實(shí)現(xiàn)。第2)種方案更具有實(shí)用性。

這種設(shè)計(jì)遵循了ISP設(shè)計(jì)原則。方法四:通過(guò)委讓實(shí)現(xiàn)Door&Door&IockQ:void9unlock.0:vuid2G周三「mGalarm?voidAdAd日就日rDesignP日tt日rnjcommonDoor.IockG);匚口mmucommonDoor.IockG);匚口mmu門(mén)口口口匚un1口?kQ*J這種方法其實(shí)是委讓方式的Adapter設(shè)計(jì)模式的實(shí)現(xiàn)。在這種方法里,AlarmDoor實(shí)現(xiàn)了Alarm接口,同時(shí)把功能lock和unlock委讓給CommonDoor對(duì)象完成。這種設(shè)計(jì)遵循了ISP設(shè)計(jì)原則。小結(jié)InterfaceSegregationPrinciple(ISP)從對(duì)接口的使用上為我們對(duì)接口抽象的顆粒度建立了判斷基準(zhǔn):在為系統(tǒng)設(shè)計(jì)接口的時(shí)候,使用多個(gè)專(zhuān)門(mén)的接口代替單一的胖接口.4依賴(lài)倒置原則RobertC.Martin氏為我們總結(jié)了在面向?qū)ο蟮脑O(shè)計(jì)(OOD)中應(yīng)該遵循的原則,這些原則被稱(chēng)為“PrinciplesofOOD",關(guān)于“PrinciplesofOOD”的相關(guān)文章可以從ObjectMenter得到。本文介紹DIP:DependencyInversionPrinciple-依賴(lài)倒置原則。有關(guān)DependencyInversionPrinciple(DIP)原文可以從這里即得到.該文提出了依賴(lài)倒置原則的2個(gè)重要方針:Highlevelmodulesshouldnotdependuponlowlevelmodules.Bothshoulddependuponabstractions.Abstractionsshouldnotdependupondetails.Detailsshoulddependuponabstractions。中文意思為:A.高層模塊不應(yīng)該依賴(lài)于低層模塊,二者都應(yīng)該依賴(lài)于抽象B.抽象不應(yīng)該依賴(lài)于細(xì)節(jié),細(xì)節(jié)應(yīng)該依賴(lài)于抽象概念解說(shuō):依賴(lài):在程序設(shè)計(jì)中,如果一個(gè)模塊a使用/調(diào)用了另一個(gè)模塊b,我們稱(chēng)模塊a依賴(lài)模塊b.高層模塊與低層模塊:往往在一個(gè)應(yīng)用程序中,我們有一些低層次的類(lèi),這些類(lèi)實(shí)現(xiàn)了一些基本的或初級(jí)的操作,我們稱(chēng)之為低層模塊;另外有一些高層次的類(lèi),這些類(lèi)封裝了某些復(fù)雜的邏輯,并且依賴(lài)于低層次的類(lèi),這些類(lèi)我們稱(chēng)之為高層模塊.為什么叫做依賴(lài)倒置(DependencyInversion)呢?面向?qū)ο蟪绦蛟O(shè)計(jì)相對(duì)于面向過(guò)程(結(jié)構(gòu)化)程序設(shè)計(jì)而言,依賴(lài)關(guān)系被倒置了。因?yàn)閭鹘y(tǒng)的結(jié)構(gòu)化程序設(shè)計(jì)中,高層模塊總是依賴(lài)于低層模塊。問(wèn)題的提出:RobertC。Martin氏在原文中給出了“BadDesign”的定義:Itishardtochangebecauseeverychangeaffectstoomanyotherpartsofthesystem。(Rigidity)系統(tǒng)很難改變,因?yàn)槊總€(gè)改變都會(huì)影響其他很多部分.Whenyoumakeachange,unexpectedpartsofthesystembreak。(Fragility)當(dāng)你對(duì)某地方做一修改,系統(tǒng)的看似無(wú)關(guān)的其他部分都不工作了。Itishardtoreuseinanotherapplicationbecauseitcannotbedisentangledfromthecurrentapplication.(Immobility)系統(tǒng)很難被另外一個(gè)應(yīng)用重用,因?yàn)槟愫茈y將要重用的部分從系統(tǒng)中分離開(kāi)來(lái).導(dǎo)致“BadDesign〃的很大原因是“高層模塊”過(guò)分依賴(lài)“低層模塊”。一個(gè)良好的設(shè)計(jì)應(yīng)該是系統(tǒng)的每一部分都是可替換的。如果“高層模塊〃過(guò)分依賴(lài)“低層模塊〃,一方面一旦“低層模塊”需要替換或者修改,“高層模塊”將受到影響;另一方面,高層模塊很難可以重用。比如,一個(gè)Copy模塊,需要把來(lái)自Keyboard的輸入復(fù)制到Print,即使對(duì)Keyboard和Print的封裝已經(jīng)做得非常好,但如果Copy模塊里直接使用Keyboard與Print,Copy任很難被其他應(yīng)用環(huán)境(比如需要輸出到磁盤(pán)時(shí))重用.問(wèn)題的解決:為了解決上述問(wèn)題,RobertC。Martin氏提出了OO設(shè)計(jì)的DependencyInversionPrinciple(DIP)原則。DIP給出了一個(gè)解決方案:在高層模塊與低層模塊之間,引入一個(gè)抽象接口層.HighLevelClasses(高層模塊) 〉A(chǔ)bstractionLayer(抽象接口層)-->LowLevelClasses(低層模塊)抽象接口是對(duì)低層模塊的抽象,低層模塊繼承或?qū)崿F(xiàn)該抽象接口。這樣,高層模塊不直接依賴(lài)低層模塊,高層模塊與低層模塊都依賴(lài)抽象接口層。當(dāng)然,抽象也不依賴(lài)低層模塊的實(shí)現(xiàn)細(xì)節(jié),低層模塊依賴(lài)(繼承或?qū)崿F(xiàn))抽象定義。RobertC。Martin氏給出的DIP方案的類(lèi)的結(jié)構(gòu)圖:PolicyLayer 〉MechanismInterface(abstract) MechanismLayer 〉UtilityInterface(abstract) UtilityLayer類(lèi)與類(lèi)之間都通過(guò)AbstractLayer來(lái)組合關(guān)系。5里氏替換原則RobertCoMartin氏為我們總結(jié)了在面向?qū)ο蟮脑O(shè)計(jì)(OOD)中應(yīng)該遵循的原則,這些原則被稱(chēng)為“PrinciplesofOOD”,關(guān)于“PrinciplesofOOD”的相關(guān)文章可以從ObjectMenter得到。本文介紹“PrinciplesofOOD〃中的里氏替換原則:LiskovSubstitutionPrinciple(LSP)??梢詮倪@里查看LiskovSubstitutionPrinciple(LSP)的原文國(guó)里氏替換原則LSP的概念解說(shuō)Functionsthatusepointersorreferencestobaseclassesmustbeabletouseobjectsofderivedclasseswithoutknowingit。所有引用基類(lèi)的地方必須能透明地使用其子類(lèi)的對(duì)象。也就是說(shuō),只有滿(mǎn)足以下2個(gè)條件的OO設(shè)計(jì)才可被認(rèn)為是滿(mǎn)足了LSP原則:一不應(yīng)該在代碼中出現(xiàn)if/else之類(lèi)對(duì)子類(lèi)類(lèi)型進(jìn)行判斷的條件。以下代碼就違反了LSP定義。if(objtypeofClassi){dosomething}elseif(objtypeofClass2){dosomethingelse)-子類(lèi)應(yīng)當(dāng)可以替換父類(lèi)并出現(xiàn)在父類(lèi)能夠出現(xiàn)的任何地方,或者說(shuō)如果我們把代碼中使用基類(lèi)的地方用它的子類(lèi)所代替,代碼還能正常工作.里氏替換原則LSP是使代碼符合開(kāi)閉原則的一個(gè)重要保證。同時(shí)LSP體現(xiàn)了:一類(lèi)的繼承原則:如果一個(gè)繼承類(lèi)的對(duì)象可能會(huì)在基類(lèi)出現(xiàn)的地方出現(xiàn)運(yùn)行錯(cuò)誤,則該子類(lèi)不應(yīng)該從該基類(lèi)繼承,或者說(shuō),應(yīng)該重新設(shè)計(jì)它們之間的關(guān)系。-動(dòng)作正確性保證:從另一個(gè)側(cè)面上保證了符合LSP設(shè)計(jì)原則的類(lèi)的擴(kuò)展不會(huì)給已有的系統(tǒng)引入新的錯(cuò)誤。類(lèi)的繼承原則:RobertC。Martin氏在介紹LiskovSubstitutionPrinciple(LSP)的原文里,舉了Rectangle和Square的例子。這里沿用這個(gè)例子,但用Java語(yǔ)言對(duì)其加以重寫(xiě),并忽略了某些細(xì)節(jié)只列出下面的精要部分來(lái)說(shuō)明里氏替換原則對(duì)類(lèi)的繼承上的約束.代碼:9,9,13.IS.19.26.30,vie.wplain匚cpYtoclbpLcandprint7classRectangle(tf-out?lewidth;dcutileheig⁢publicdoublegetHeightf){returnheight;}publicvsidsetHeight(dcu-bieheight){this.height=height;}publicdoublegetWidth(){returnwidth;1^^publicuaidSetWldttl(doubleWidth){this.width=width;}}clssiSquareeytendsRectangle{publicvoidsetHeight(rfoubl.eheight){super.5etHeight(heig_ht);super.setwidth(height);}publiciroidsstWldttl(bou:t)l&width){super.setHeight(w=idth);super,setwidth(width)j這里Rectangle是基類(lèi),Square從Rectangle繼承。這種繼承關(guān)系有什么問(wèn)題嗎?假如已有的系統(tǒng)中存在以下既有的業(yè)務(wù)邏輯代碼:voidg(Rectangler){r。setWidth(5);r。setHeight⑷;if(r.getWidth()*r.getHeight()!=20) {thrownewRuntimeException();})則對(duì)應(yīng)于擴(kuò)展類(lèi)Square,在調(diào)用既有業(yè)務(wù)邏輯時(shí):Rectanglesquare=newSquare();g(square);時(shí)會(huì)拋出一個(gè)RuntimeException異常。這顯然違反了LSP原則.動(dòng)作正確性保證:因?yàn)長(zhǎng)SP對(duì)子類(lèi)的約束,所以為已存在的類(lèi)做擴(kuò)展構(gòu)造一個(gè)新的子類(lèi)時(shí),根據(jù)LSP的定義,不會(huì)給已有的系統(tǒng)引入新的錯(cuò)誤.DesignbyContract根據(jù)BertrandMeyer氏提出的DesignbyContract(DBC:基于合同的設(shè)計(jì))概念的描述,對(duì)于類(lèi)的一個(gè)方法,都有一個(gè)前提條件以及一個(gè)后續(xù)條件,前提條件說(shuō)明方法接受什么樣的參數(shù)數(shù)據(jù)等,只有前提條件得到滿(mǎn)足時(shí),這個(gè)方法才能被調(diào)用;同時(shí)后續(xù)條件用來(lái)說(shuō)明這個(gè)方法完成時(shí)的狀態(tài),如果一個(gè)方法的執(zhí)行會(huì)導(dǎo)致這個(gè)方法的后續(xù)條件不成立,那么這個(gè)方法也不應(yīng)該正常返回?,F(xiàn)在把前提條件以及后續(xù)條件應(yīng)用到繼承子類(lèi)中,子類(lèi)方法應(yīng)該滿(mǎn)足:1)前提條件不強(qiáng)于基類(lèi).2)后續(xù)條件不弱于基類(lèi).換句話(huà)說(shuō),通過(guò)基類(lèi)的接口調(diào)用一個(gè)對(duì)象時(shí),用戶(hù)只知道基類(lèi)前提條件以及后續(xù)條件。因此繼承類(lèi)不得要求用戶(hù)提供比基類(lèi)方法要求的更強(qiáng)的前提條件,亦即,繼承類(lèi)方法必須接受任何基類(lèi)方法能接受的任何條件(參數(shù)).同樣,繼承類(lèi)必須順從基類(lèi)的所有后續(xù)條件,亦即,繼承類(lèi)方法的行為和輸出不得違反由基類(lèi)建立起來(lái)的任何約束,不能讓用戶(hù)對(duì)繼承類(lèi)方法的輸出感到困惑。這樣,我們就有了基于合同的LSP,基于合同的LSP是LSP的一種強(qiáng)化。在很多情況下,在設(shè)計(jì)初期我們類(lèi)之間的關(guān)系不是很明確,LSP則給了我們一個(gè)判斷和設(shè)計(jì)類(lèi)之間關(guān)系的基準(zhǔn):需不需要繼承,以及怎樣設(shè)計(jì)繼承關(guān)系。三,針對(duì)接口編程,而不是針對(duì)實(shí)現(xiàn)編程在面向?qū)ο笤O(shè)計(jì)方法中有很多值得提倡的方法,這些方法可以為我們的設(shè)計(jì)帶來(lái)很大的靈活性,可復(fù)用性。其中一個(gè)原則就是“針對(duì)接口編程,而不是針對(duì)實(shí)現(xiàn)編程”這個(gè)原則帶來(lái)的好處有以下幾點(diǎn):Client不必知道其使用對(duì)象的具體所屬類(lèi)。Client無(wú)需知道特定類(lèi),只需知道他們所期望的接口。一個(gè)對(duì)象可以很容易地被(實(shí)現(xiàn)了相同接口的)的另一個(gè)對(duì)象所替換.對(duì)象間的連接不必硬綁定(hardwire)到一個(gè)具體類(lèi)的對(duì)象上,因此增加了靈活性。松散藕合(loosens coupling)。增加了重用的可能性。提高了(對(duì)象)組合的機(jī)率,因?yàn)楸话瑢?duì)象可以是任何實(shí)現(xiàn)了一個(gè)指定接口的類(lèi)。但從辯證法的角度看,事物總有利有弊.“針對(duì)接口編程〃有如上諸多好處,卻不可避免的帶來(lái)設(shè)計(jì)的復(fù)雜性.特別對(duì)于沒(méi)有豐富經(jīng)驗(yàn)的設(shè)計(jì)人員。其中令我比較困惑的地方是:要想針對(duì)接口編程,就必然要最大化接口類(lèi),使包括所有子類(lèi)的方法,這樣我們才能利用多態(tài)性用接口類(lèi)來(lái)實(shí)現(xiàn)操作子類(lèi)。但這會(huì)帶來(lái)以下幾點(diǎn)不足.違反面向?qū)ο蟮牧硪粋€(gè)原則,這個(gè)原則是:一個(gè)類(lèi)只能定義那些對(duì)它的子類(lèi)有意義的操作。接口類(lèi)包括了并不是對(duì)每一個(gè)子類(lèi)都有意義的方法,使接口類(lèi)臃腫,難以理解。從父類(lèi)繼承的無(wú)用方法,如何處理。敏捷開(kāi)發(fā)提倡簡(jiǎn)單設(shè)計(jì)的實(shí)踐,”并在實(shí)現(xiàn)新需求時(shí)抓住機(jī)會(huì)改進(jìn)設(shè)計(jì)”以對(duì)同類(lèi)性質(zhì)的改動(dòng)封閉,做到由需求的變化驅(qū)動(dòng)設(shè)計(jì)的進(jìn)化(我們不能因?yàn)樵O(shè)計(jì)的退化而責(zé)怪需求的變化),同時(shí)經(jīng)驗(yàn)在此起到十分重要的作用,如有經(jīng)驗(yàn)的設(shè)計(jì)人員可以憑經(jīng)驗(yàn)在初始設(shè)計(jì)時(shí)做出必要的抽象來(lái)滿(mǎn)足ocp原則等,或是在需求變動(dòng)時(shí)確定系統(tǒng)所需的抽象(所需的封閉),當(dāng)然應(yīng)及早的刺激這種變化的出現(xiàn)(如測(cè)試驅(qū)動(dòng)的開(kāi)發(fā)方法)。OOD承諾了一系列的好處(靈活性可重用性可維護(hù)性),用OO語(yǔ)言設(shè)計(jì)開(kāi)發(fā),若要方便的得到這些所謂的好處,有一系列的原則是要遵循的,如SRP,OCP,LSP,ISP等.SRP(單一職責(zé)原則)維護(hù)類(lèi)的簡(jiǎn)單性,類(lèi)不應(yīng)承擔(dān)一個(gè)以上令其變化的原因,否則應(yīng)考慮分離并重新構(gòu)造類(lèi),但如果的應(yīng)用的變化方式總是導(dǎo)致類(lèi)中的職責(zé)同時(shí)變化,卻沒(méi)必要分離他們Ocp(開(kāi)閉原則)使OO系統(tǒng)做到對(duì)擴(kuò)展開(kāi)放,對(duì)修改封閉。OCP的遵循關(guān)鍵在于抽象,其主要實(shí)現(xiàn)方式有:定義接口描繪所需的操作,client只需關(guān)注接口的調(diào)用,子類(lèi)型可以以任何其選擇的方式實(shí)現(xiàn)接口,即所謂的stategy模式,或者定義抽象類(lèi)并于其中實(shí)現(xiàn)公共操作,個(gè)性操作定義為abstract或virtual,由子類(lèi)型負(fù)責(zé)個(gè)性化實(shí)現(xiàn),通過(guò)此兩法,將功能的通用部分和實(shí)現(xiàn)細(xì)節(jié)分離出來(lái)。當(dāng)然,設(shè)計(jì)人員應(yīng)該確定(猜測(cè)或憑經(jīng)驗(yàn))系統(tǒng)對(duì)哪種變化做到封閉,因?yàn)椴豢赡軐?duì)所有變化做到封閉,如《敏捷模式實(shí)踐》中提到shape類(lèi)型排序處理問(wèn)題,為做到對(duì)排序安排(或變動(dòng))的封閉(使得各子類(lèi)型間無(wú)需相互知曉,也可以做到自由安排排序順序),選擇使用“數(shù)據(jù)驅(qū)動(dòng)”的方式(即單獨(dú)構(gòu)造結(jié)構(gòu)表示排序安排-其中以子類(lèi)類(lèi)型在結(jié)構(gòu)中的排列位置表先后),于shape基類(lèi)中實(shí)現(xiàn)一次Precedes操作即可,子類(lèi)型無(wú)需分別實(shí)現(xiàn).OCP作為OOD核心所在依賴(lài)抽象來(lái)實(shí)現(xiàn),但敏捷設(shè)計(jì)(或者說(shuō)好的設(shè)計(jì))拒絕不成熟的抽象,程序僅應(yīng)對(duì)頻繁變化的部分做出抽象.Liskov替換原則是使得ocp成為可能的原則之一,強(qiáng)調(diào)“子類(lèi)型subtype必須能夠替換掉它們的基類(lèi)型basetype",控制OO的繼承關(guān)系安排,在OOD用is—a來(lái)確定類(lèi)間的繼承關(guān)系,LSP指出這種is—a關(guān)系是就行為方式(即類(lèi)的各操作)而言的,而行為方式是可以合理假設(shè)的,是客戶(hù)程序所依賴(lài)的。為遵循LSP,可借用DBC(designbycontract)的操作前置條件和后置條件,“要使操作得以執(zhí)行,前置條件必須為真,執(zhí)行完畢后,該操作要確保后置條件為真”(為每個(gè)方法注明其前置和后置條件十分有幫助),如此,則“在重新聲明派生類(lèi)中操作時(shí),只能使用相等或更弱的前置條件來(lái)替換原始的前置條件,只能用相等或更強(qiáng)的后置條件來(lái)替換原始的后置條件"(interface和其實(shí)現(xiàn)類(lèi)間抽象方法和其實(shí)現(xiàn)此二者一定滿(mǎn)足前述條件)。同時(shí)亦可用前述ocp遵循所用的二模式使設(shè)計(jì)符合LSP,另外子類(lèi)型中的異常拋出應(yīng)考慮在遵循LSP的范圍內(nèi)。關(guān)于提取公共部分的設(shè)計(jì)工具:”提取公共職責(zé)放入超類(lèi)中,稍后添加的新的子類(lèi)型可能會(huì)以新的方式支持同樣的職責(zé),此時(shí)原來(lái)的超類(lèi)可能會(huì)是一個(gè)抽象類(lèi)”.DIP(依賴(lài)倒置原則),作為framework的設(shè)計(jì)核心,其相對(duì)于傳統(tǒng)軟件設(shè)計(jì)而言,通常(傳統(tǒng))軟件設(shè)計(jì)中采用結(jié)構(gòu)化設(shè)計(jì)用高層模塊直接調(diào)用底層模塊,這樣高層模塊將嚴(yán)重依賴(lài)于底層模塊的變動(dòng),在OOD中通過(guò)為高層模塊定義所需使用的服務(wù)接口,底層模塊現(xiàn)實(shí)這樣的接口,高層模塊通過(guò)抽象接口使用下一層(strategy模式所聲明的),如此看來(lái)接口的擁有者一般是其使用者而非其實(shí)現(xiàn)者。通常為了滿(mǎn)足DIP一良好OOD的基本底層機(jī)制,我么需要找出系統(tǒng)中潛在的抽象,而抽象通常是那些不隨具體細(xì)節(jié)變化而變化的東東。ISP接口隔離原則,如SRP維護(hù)類(lèi)的簡(jiǎn)單性一樣,ISP用于維護(hù)接口的簡(jiǎn)單和必要性,因?yàn)榻涌谑菫榭蛻?hù)調(diào)用的,因此其應(yīng)該是“大小尺寸合適的”,“胖〃接口顯然對(duì)調(diào)用者造成累贅,ISP則用于將“胖”接口分離成多個(gè)合適的接口.當(dāng)然,在系統(tǒng)設(shè)計(jì)實(shí)現(xiàn)中要做到這些并非容易,單單知道其存在未必做得到將其實(shí)現(xiàn)到系統(tǒng)中,開(kāi)發(fā)經(jīng)驗(yàn)的積累同樣重要,但早些知道存在個(gè)意識(shí)并在做時(shí)將其考慮進(jìn)去,也是積累,慢慢來(lái)吧,n多事要做吶。.面向接口編程詳解—-思想基礎(chǔ)我想,對(duì)于各位使用面向?qū)ο缶幊陶Z(yǔ)言的程序員來(lái)說(shuō),“接口”這個(gè)名詞一定不陌生,但是不知各位有沒(méi)有這樣的疑惑:接口有什么用途?它和抽象類(lèi)有什么區(qū)別?能不能用抽象類(lèi)代替接口呢?而且,作為程序員,一定經(jīng)常聽(tīng)到“面向接口編程”這個(gè)短語(yǔ),那么它是什么意思?有什么思想內(nèi)涵?和面向?qū)ο缶幊淌鞘裁搓P(guān)系?本文將一一解答這些疑問(wèn)。1.面向接口編程和面向?qū)ο缶幊淌鞘裁搓P(guān)系首先,面向接口編程和面向?qū)ο缶幊滩⒉皇瞧郊?jí)的,它并不是比面向?qū)ο缶幊谈冗M(jìn)的一種獨(dú)立的編程思想,而是附屬于面向?qū)ο笏枷塍w系,屬于其一部分?;蛘哒f(shuō),它是面向?qū)ο缶幊腆w系中的思想精髓之一。2。接口的本質(zhì)接口,在表面上是由幾個(gè)沒(méi)有主體代碼的方法定義組成的集合體,有唯一的名稱(chēng),可以被類(lèi)或其他接口所實(shí)現(xiàn)(或者也可以說(shuō)繼承)。它在形式上可能是如下的樣子:interfaceInterfaceName日國(guó){voidMethod1();voidMethod2(intpara1);voidMethod3(stringpara2,stringpara3);那么,接口的本質(zhì)是什么呢?或者說(shuō)接口存在的意義是什么。我認(rèn)為可以從以下兩個(gè)視角考慮:1)接口是一組規(guī)則的集合,它規(guī)定了實(shí)現(xiàn)本接口的類(lèi)或接口必須擁有的一組規(guī)則.體現(xiàn)了自然界“如果你是……則必須能……”的理念。例如,在自然界中,人都能吃飯,即“如果你是人,則必須能吃飯〃。那么模擬到計(jì)算機(jī)程序中,就應(yīng)該有一個(gè)IPerson(習(xí)慣上,接口名由“I"開(kāi)頭)接口,并有一個(gè)方法叫Eat(),然后我們規(guī)定,每一個(gè)表示“人”的類(lèi),必須實(shí)現(xiàn)IPerson接口,這就模擬了自然界“如果你是人,則必須能吃飯”這條規(guī)則。從這里,我想各位也能看到些許面向?qū)ο笏枷氲臇|西。面向?qū)ο笏枷氲暮诵闹?,就是模擬真實(shí)世界,把真實(shí)世界中的事物抽象成類(lèi),整個(gè)程序靠各個(gè)類(lèi)的實(shí)例互相通信、互相協(xié)作完成系統(tǒng)功能,這非常符合真實(shí)世界的運(yùn)行狀況,也是面向?qū)ο笏枷氲木琛?)接口是在一定粒度視圖上同類(lèi)事物的抽象表示。注意這里我強(qiáng)調(diào)了在一定粒度視圖上,因?yàn)椤巴?lèi)事物”這個(gè)概念是相對(duì)的,它因?yàn)榱6纫晥D不同而不同。例如,在我的眼里,我是一個(gè)人,和一頭豬有本質(zhì)區(qū)別我可以接受我和我同學(xué)是同類(lèi)這個(gè)說(shuō)法,但絕不能接受我和一頭豬是同類(lèi)。但是,如果在一個(gè)動(dòng)物學(xué)家眼里,我和豬應(yīng)該是同類(lèi),因?yàn)槲覀兌际莿?dòng)物,他可以認(rèn)為“人”和“豬”都實(shí)現(xiàn)了lAnimal這個(gè)接口,而他在研究動(dòng)物行為時(shí),不會(huì)把我和豬分開(kāi)對(duì)待,而會(huì)從“動(dòng)物”這個(gè)較大的粒度上研究,但他會(huì)認(rèn)為我和一棵樹(shù)有本質(zhì)區(qū)別?,F(xiàn)在換了一個(gè)遺傳學(xué)家,情況又不同了,因?yàn)樯锒寄苓z傳,所以在他眼里我不僅和豬沒(méi)區(qū)別,和一只蚊子、一個(gè)細(xì)菌、一顆樹(shù)、一個(gè)蘑菇乃至一個(gè)SARS病毒都沒(méi)什么區(qū)別,因?yàn)樗麜?huì)認(rèn)為我們都實(shí)現(xiàn)了IDescendable這個(gè)接口(注:descendvi。遺傳),即我們都是可遺傳的東西,他不會(huì)分別研究我們,而會(huì)將所有生物作為同類(lèi)進(jìn)行研究,在他眼里沒(méi)有人和病毒之分,只有可遺傳的物質(zhì)和不可遺傳的物質(zhì)。但至少,我和一塊石頭還是有區(qū)別的。可不幸的事情發(fā)生了,某日,地球上出現(xiàn)了一位偉大的人,他叫列寧,他在熟讀馬克思、恩格斯的辯證唯物主義思想巨著后頗有心得,于是他下了一個(gè)著名的定義:所謂物質(zhì),就是能被意識(shí)所反映的客觀(guān)實(shí)在。至此,我和一塊石頭、一絲空氣、一條成語(yǔ)和傳輸手機(jī)信號(hào)的電磁場(chǎng)已經(jīng)沒(méi)什么區(qū)別了,因?yàn)樵诹袑幍难劾?,我們都是可以被意識(shí)所反映的客觀(guān)實(shí)在。如果列寧是一名程序員,他會(huì)這么說(shuō):所謂物質(zhì),就是所有同時(shí)實(shí)現(xiàn)了“IReflectabe”和“IEsse”兩個(gè)接口的類(lèi)所生成的實(shí)例。(注:reflectv。反映essen.客觀(guān)實(shí)在)也許你會(huì)覺(jué)得我上面的例子像在瞎掰,但是,這正是接口得以存在的意義。面向?qū)ο笏枷牒秃诵闹唤凶龆鄳B(tài)性,什么叫多態(tài)性?說(shuō)白了就是在某個(gè)粒度視圖層面上對(duì)同類(lèi)事物不加區(qū)別的對(duì)待而統(tǒng)一處理.而之所以敢這樣做,就是因?yàn)橛薪涌诘拇嬖?。像那個(gè)遺傳學(xué)家,他明白所有生物都實(shí)現(xiàn)了IDescendable接口,那只要是生物,一定有Descend()這個(gè)方法,于是他就可以統(tǒng)一研究,而不至于分別研究每一種生物而最終累死??赡苓@里還不能給你一個(gè)關(guān)于接口本質(zhì)和作用的直觀(guān)印象。那么在后文的例子和對(duì)幾個(gè)設(shè)計(jì)模式的解析中,你將會(huì)更直觀(guān)體驗(yàn)到接口的內(nèi)涵。3.面向接口編程綜述通過(guò)上文,我想大家對(duì)接口和接口的思想內(nèi)涵有了一個(gè)了解,那么什么是面向接口編程呢?我個(gè)人的定義是:在系統(tǒng)分析和架構(gòu)中,分清層次和依賴(lài)關(guān)系,每個(gè)層次不是直接向其上層提供服務(wù)(即不是直接實(shí)例化在上層中),而是通過(guò)定義一組接口,僅向上層暴露其接口功能,上層對(duì)于下層僅僅是接口依賴(lài),而不依賴(lài)具體類(lèi)。這樣做的好處是顯而易見(jiàn)的,首先對(duì)系統(tǒng)靈活性大有好處。當(dāng)下層需要改變時(shí),只要接口及接口功能不變,則上層不用做任何修改。甚至可以在不改動(dòng)上層代碼時(shí)將下層整個(gè)替換掉,就像我們將一個(gè)WD的60G硬盤(pán)換成一個(gè)希捷的160G的硬盤(pán),計(jì)算機(jī)其他地方不用做任何改動(dòng),而是把原硬盤(pán)拔下來(lái)、新硬盤(pán)插上就行了,因?yàn)橛?jì)算機(jī)其他部分不依賴(lài)具體硬盤(pán)而只依賴(lài)一個(gè)IDE接口,只要硬盤(pán)實(shí)現(xiàn)了這個(gè)接口,就可以替換上去。從這里看,程序中的接口和現(xiàn)實(shí)中的接口極為相似,所以我一直認(rèn)為,接口(interface)這個(gè)詞用的真是神似!使用接口的另一個(gè)好處就是不同部件或?qū)哟蔚拈_(kāi)發(fā)人員可以并行開(kāi)工,就像造硬盤(pán)的不用等造CPU的,也不用等造顯示器的,只要接口一致,設(shè)計(jì)合理,完全可以并行進(jìn)行開(kāi)發(fā),從而提高效率.本篇文章先到這里。最后我想再啰嗦一句:面向?qū)ο蟮木枋悄M現(xiàn)實(shí),這也可以說(shuō)是我這篇文章的靈魂。所以多從現(xiàn)實(shí)中思考面向?qū)ο蟮臇|西,對(duì)提高系統(tǒng)分析設(shè)計(jì)能力大有脾益。仔細(xì)看了各位的回復(fù),非常高興能和大家一起討論技術(shù)問(wèn)題.感謝給出肯定的朋友,也要感謝提出意見(jiàn)和質(zhì)疑的朋友,這促使我更深入思考一些東西,希望能借此進(jìn)步.在這里我想補(bǔ)充一些東西,以討論一些回復(fù)中比較集中的問(wèn)題。.關(guān)于“面向接口編程”中的“接口”與具體面向?qū)ο笳Z(yǔ)言中“接口”兩個(gè)詞看到有朋友提出“面向接口編程”中的“接口〃二字應(yīng)該比單純編程語(yǔ)言中的interface范圍更大.我經(jīng)過(guò)思考,覺(jué)得很有道理。這里我寫(xiě)的確實(shí)不太合理。我想,面向?qū)ο笳Z(yǔ)言中的“接口”是指具體的一種代碼結(jié)構(gòu),例如C#中用interface關(guān)鍵字定義的接口.而“面向接口編程”中的“接口”可以說(shuō)是一種從軟件架構(gòu)的角度、從一個(gè)更抽象的層面上指那種用于隱藏具體底層類(lèi)和實(shí)現(xiàn)多態(tài)性的結(jié)構(gòu)部件。從這個(gè)意義上說(shuō),如果定義一個(gè)抽象類(lèi),并且目的是為了實(shí)現(xiàn)多態(tài),那么我認(rèn)為把這個(gè)抽象類(lèi)也稱(chēng)為“接口〃是合理的。但是用抽象類(lèi)實(shí)現(xiàn)多態(tài)合理不合理?在下面第二條討論。概括來(lái)說(shuō),我覺(jué)得兩個(gè)“接口”的概念既相互區(qū)別又相互聯(lián)系?!泵嫦蚪涌诰幊獭敝械慕涌谑且环N思想層面的用于實(shí)現(xiàn)多態(tài)性、提高軟件靈活性和可維護(hù)性的架構(gòu)部件,而具體語(yǔ)言中的“接口”是將這種思想中的部件具體實(shí)施到代碼里的手段。.關(guān)于抽象類(lèi)與接口看到回復(fù)中這是討論的比較激烈的一個(gè)問(wèn)題。很抱歉我考慮不周沒(méi)有在文章中討論這個(gè)問(wèn)題。我個(gè)人對(duì)這個(gè)問(wèn)題的理解如下:如果單從具體代碼來(lái)看,對(duì)這兩個(gè)概念很容易模糊,甚至覺(jué)得接口就是多余的,因?yàn)閱螐木唧w功能來(lái)看,除多重繼承外(C#,Java中),抽象類(lèi)似乎完全能取代接口.但是,難道接口的存在是為了實(shí)現(xiàn)多重繼承?當(dāng)然不是。我認(rèn)為,抽象類(lèi)和接口的區(qū)別在于使用動(dòng)機(jī).使用抽象類(lèi)是為了代碼的復(fù)用,而使用接口的動(dòng)機(jī)是為了實(shí)現(xiàn)多態(tài)性.所以,如果你在為某個(gè)地方該使用接口還是抽象類(lèi)而猶豫不決時(shí),那么可以想想你的動(dòng)機(jī)是什么.看到有朋友對(duì)IPerson這個(gè)接口的質(zhì)疑,我個(gè)人的理解是,IPerson這個(gè)接口該不該定義,關(guān)鍵看具體應(yīng)用中是怎么個(gè)情況。如果我們的項(xiàng)目中有Women和Man,都繼承Person,而且Women和Man絕大多數(shù)方法都相同,只有一個(gè)方法DoSomethingInWC()不同(例子比較粗俗,各位見(jiàn)諒),那么當(dāng)然定義一個(gè)AbstractPerson抽象類(lèi)比較合理,因?yàn)樗梢园哑渌蟹椒ǘ及M(jìn)去,子類(lèi)只定義DoSomethingInWC(),大大減少了重復(fù)代碼量。但是,如果我們程序中的Women和Man兩個(gè)類(lèi)基本沒(méi)有共同代碼,而且有一個(gè)PersonHandle類(lèi)需要實(shí)例化他們,并且不希望知道他們是男是女,而只需把他們當(dāng)作人看待,并實(shí)現(xiàn)多態(tài),那么定義成接口就有必要了。總而言之,接口與抽象類(lèi)的區(qū)別主要在于使用的動(dòng)機(jī),而不在于其本身。而一個(gè)東西該定義成抽象類(lèi)還是接口,要根據(jù)具體環(huán)境的上下文決定。再者,我認(rèn)為接口和抽象類(lèi)的另一個(gè)區(qū)別在于,抽象類(lèi)和它的子類(lèi)之間應(yīng)該是一般和特殊的關(guān)系,而接口僅僅是它的子類(lèi)應(yīng)該實(shí)現(xiàn)的一組規(guī)則。(當(dāng)然,有時(shí)也可能存在一般與特殊的關(guān)系,但我們使用接口的目的不在這里)如,交通工具定義成抽象類(lèi),汽車(chē)、飛機(jī)、輪船定義成子類(lèi),是可以接受的,因?yàn)槠?chē)、飛機(jī)、輪船都是一種特殊的交通工具。再譬如Icomparable接口,它只是說(shuō),實(shí)現(xiàn)這個(gè)接口的類(lèi)必須要可以進(jìn)行比較,這是一條規(guī)則。如果Car這個(gè)類(lèi)實(shí)現(xiàn)了Ic。mparable,只是說(shuō),我們的Car中有一個(gè)方法可以對(duì)兩個(gè)Car的實(shí)例進(jìn)行比較,可能是比哪輛車(chē)更貴,也可能比哪輛車(chē)更大,這都無(wú)所謂,但我們不能說(shuō)“汽車(chē)是一種特殊的可以比較”,這在文法上都不通。五.面向接口編程詳解問(wèn)題的提出定義:現(xiàn)在我們要開(kāi)發(fā)一個(gè)應(yīng)用,模擬移動(dòng)存儲(chǔ)設(shè)備的讀寫(xiě),即計(jì)算機(jī)與U盤(pán)、MP3、移動(dòng)硬盤(pán)等設(shè)備進(jìn)行數(shù)據(jù)交換。上下文(環(huán)境):已知要實(shí)現(xiàn)U盤(pán)、MP3播放器、移動(dòng)硬盤(pán)三種移動(dòng)存儲(chǔ)設(shè)備,要求計(jì)算機(jī)能同這三種設(shè)備進(jìn)行數(shù)據(jù)交換,并且以后可能會(huì)有新的第三方的移動(dòng)存儲(chǔ)設(shè)備,所以計(jì)算機(jī)必須有擴(kuò)展性,能與目前未知而以后可能會(huì)出現(xiàn)的存儲(chǔ)設(shè)備進(jìn)行數(shù)據(jù)交換.各個(gè)存儲(chǔ)設(shè)備間讀、寫(xiě)的實(shí)現(xiàn)方法不同,U盤(pán)和移動(dòng)硬盤(pán)只有這兩個(gè)方法,MP3Player還有一個(gè)PlayMusic方法。名詞定義:數(shù)據(jù)交換={讀,寫(xiě)}看到上面的問(wèn)題,我想各位腦子中一定有了不少想法,這是個(gè)很好解決的問(wèn)題,很多方案都能達(dá)到效果。下面,我列舉幾個(gè)典型的方案。解決方案列舉方案一:分別定義FlashDisk、MP3Player、MobileHardDisk三個(gè)類(lèi),實(shí)現(xiàn)各自的Read和Write方法。然后在Computer類(lèi)中實(shí)例化上述三個(gè)類(lèi),為每個(gè)類(lèi)分別寫(xiě)讀、寫(xiě)方法.例如,為FlashDisk寫(xiě)ReadFromFlashDisk、WriteToFlashDisk兩個(gè)方法。總共六個(gè)方法。方案二:定義抽象類(lèi)MobileStorage,在里面寫(xiě)虛方法Read和Write,m個(gè)存儲(chǔ)設(shè)備繼承此抽象類(lèi),并重寫(xiě)Read和Write方法。Computer類(lèi)中包含一個(gè)類(lèi)型為MobileStorage的成員變量,并為其編寫(xiě)get/set器,這樣Computer中只需要兩個(gè)方法:ReadData和WriteData,并通過(guò)多態(tài)性實(shí)現(xiàn)不同移動(dòng)設(shè)備的讀寫(xiě)。方案三:與方案二基本相同,只是不定義抽象類(lèi),而是定義接口IMobileStorage,移動(dòng)存儲(chǔ)器類(lèi)實(shí)現(xiàn)此接口。Computer中通過(guò)依賴(lài)接口IMobileStorage實(shí)現(xiàn)多態(tài)性。方案四:定義接口IReadable和IWritable,兩個(gè)接口分別只包含Read和Write,然后定義接口IMobileStorage接口繼承自IReadable和IWritable,剩下的實(shí)現(xiàn)與方案三相同.下面,我們來(lái)分析一下以上四種方案:首先,方案一最直白,實(shí)現(xiàn)起來(lái)最簡(jiǎn)單但是它有一個(gè)致命的弱點(diǎn):可擴(kuò)展性差?;蛘哒f(shuō),不符合“開(kāi)放-關(guān)閉原則”(注:意為對(duì)擴(kuò)展開(kāi)放,對(duì)修改關(guān)閉)。當(dāng)將來(lái)有了第三方擴(kuò)展移動(dòng)存儲(chǔ)設(shè)備時(shí),必須對(duì)Computer進(jìn)行修改。這就如在一個(gè)真實(shí)的計(jì)算機(jī)上,為每一種移動(dòng)存儲(chǔ)設(shè)備實(shí)現(xiàn)一個(gè)不同的插口、并分別有各自的驅(qū)動(dòng)程序.當(dāng)有了一種新的移動(dòng)存儲(chǔ)設(shè)備后,我們就要將計(jì)算機(jī)大卸八塊,然后增加一個(gè)新的插口,在編寫(xiě)一套針對(duì)此新設(shè)備的驅(qū)動(dòng)程序。這種設(shè)計(jì)顯然不可取.此方案的另一個(gè)缺點(diǎn)在于,冗余代碼多。如果有100種移動(dòng)存儲(chǔ),那我們的Computer中豈不是要至少寫(xiě)200個(gè)方法,這是不能接受的!我們?cè)賮?lái)看方案二和方案三,之所以將這兩個(gè)方案放在一起討論,是因?yàn)樗麄兓臼且粋€(gè)方案(從思想層面上來(lái)說(shuō),只不過(guò)實(shí)現(xiàn)手段不同,一個(gè)是使用了抽象類(lèi),一個(gè)是使用了接口,而且最終達(dá)到的目的應(yīng)該是一樣的.我們先來(lái)評(píng)價(jià)這種方案:首先它解決了代碼冗余的問(wèn)題,因?yàn)榭梢詣?dòng)態(tài)替換移動(dòng)設(shè)備,并且都實(shí)現(xiàn)了共同的接口,所以不管有多少種移動(dòng)設(shè)備,只要一個(gè)Read方法和一個(gè)Write方法,多態(tài)性就幫我們解決問(wèn)題了.而對(duì)第一個(gè)問(wèn)題,由于可以運(yùn)行時(shí)動(dòng)態(tài)替換,而不必將移動(dòng)存儲(chǔ)類(lèi)硬編碼在Computer中,所以有了新的第三方設(shè)備,完全可以替換進(jìn)去運(yùn)行。這就是所謂的“依賴(lài)接口,而不是依賴(lài)與具體類(lèi)”,不信你看看,Computer類(lèi)只有一個(gè)MobileStorage類(lèi)型或IMobileStorage類(lèi)型的成員變量,至于這個(gè)變量具體是什么類(lèi)型,它并不知道,這取決于我們?cè)谶\(yùn)行時(shí)給這個(gè)變量的賦值。如此一來(lái),Computer和移動(dòng)存儲(chǔ)器類(lèi)的耦合度大大下降。那么這里該選抽象類(lèi)還是接口呢?還記得第一篇文章我對(duì)抽象類(lèi)和接口選擇的建議嗎?看動(dòng)機(jī).這里,我們的動(dòng)機(jī)顯然是實(shí)現(xiàn)多態(tài)性而不是為了代碼復(fù)用,所以當(dāng)然要用接口。最后我們?cè)賮?lái)看一看方案四,它和方案三很類(lèi)似,只是將“可讀”和“可寫(xiě)〃兩個(gè)規(guī)則分別抽象成了接口,然后讓IMobileStorage再繼承它們。這樣做,顯然進(jìn)一步提高了靈活性,但是,這有沒(méi)有設(shè)計(jì)過(guò)度的嫌疑呢?我的觀(guān)點(diǎn)是:這要看具體情況。如果我們的應(yīng)用中可能會(huì)出現(xiàn)一些類(lèi),這些類(lèi)只實(shí)現(xiàn)讀方法或只實(shí)現(xiàn)寫(xiě)方法,如只讀光盤(pán),那么這樣做也是可以的。如果我們知道以后出現(xiàn)的東西都是能讀又能寫(xiě)的,那這兩個(gè)接口就沒(méi)有必要了.其實(shí)如果將只讀設(shè)備的Write方法留空或拋出異常,也可以不要這兩個(gè)接口??傊痪湓?huà):理論是死的,人是活的,一切從現(xiàn)實(shí)需要來(lái),防止設(shè)計(jì)不足,也要防止設(shè)計(jì)過(guò)度。在這里,我們姑且認(rèn)為以后的移動(dòng)存儲(chǔ)都是能讀又能寫(xiě)的,所以我們選方案三.實(shí)現(xiàn)下面,我們要將解決方案加以實(shí)現(xiàn).我選擇的語(yǔ)言是C#,但是在代碼中不會(huì)用到C#特有的性質(zhì),所以使用其他語(yǔ)言的朋友一樣可以參考。首先編寫(xiě)IMobileStorage接口:1namespaceInterfaceExample2日?qǐng)D{3 publicinterfaceIMobileStorage4申申 {voidRead();〃從自身讀數(shù)據(jù)voidWrite();〃將數(shù)據(jù)寫(xiě)入自身}}比較簡(jiǎn)單,只有兩個(gè)方法,沒(méi)什么好說(shuō)的,接下來(lái)是三個(gè)移動(dòng)存儲(chǔ)設(shè)備的具體實(shí)現(xiàn)代碼:U盤(pán)1namespaceInterfaceExample2日叫34百申56百申789101112韓131415卜16卜17}publicclassFlashDisk:IMobileStorage{publicvoidRead(){Console。WriteLine("ReadingfromFlashDisk ")Console.WriteLine("Readfinished!”);}publicvoidWrite(){Console。WriteLine("WritingtoFlashDisk ");Console.WriteLine("Writefinished!”);))MP31namespaceInterfaceExample2臼國(guó){3 publicclassMP3Player:IMobileStorage4部 {5 publicvoidRead()66百申789101112中申131415卜161718中申1920卜21卜22}{TOC\o"1-5"\h\zConsole。WriteLine("ReadingfromMP3Player ");Console.WriteLine("Readfinished!”);)publicvoidWrite(){Console.WriteLine("WritingtoMP3Player ");Console.WriteLine("Writefinished!”);)publicvoidPlayMusic(){Console.WriteLine("Musicisplaying ");)移動(dòng)硬盤(pán)1namespaceInterfaceExample2臼國(guó){3 publicclassMobileHardDisk:IMobileStorage肅申891011{publicvoidRead(){Console。WriteLine("ReadingfromMobileHardDiskConsole.WriteLine("Readfinished!”);}12中申131415卜16卜17}publicvoidWrite(){Console.WriteLine("WritingtoMobileHardDisk ");Console.WriteLine("Writefinished!");))F面,我們來(lái)寫(xiě)Computer:可以看到,它們都實(shí)現(xiàn)YlMobileStorage接口,并重寫(xiě)了各自不同的Read和F面,我們來(lái)寫(xiě)Computer:1namespaceInterfaceExample2日國(guó){3 publicclassComputer4百申 {I privateIMobileStorage_usbDrive;778百申910韓1112卜13144申1516卜17卜181920中申21卜222324中申2526卜272829申申publicIMobileStorageUsbDrive

{get{returnthis._usbDrive;}set{this。_usbDrive=value;))publicComputer(){)publicComputer(IMobileStorageusbDrive){this.UsbDrive=usbDrive;)publicvoidReadData(){

30I this._usbDrive.Read();TOC\o"1-5"\h\z31卜 }3233I publicvoidWriteData()34申申 {35I this._usbDrive。Write();36卜 }37卜}38}其中的UsbDrive就是可替換的移動(dòng)存儲(chǔ)設(shè)備,之所以用這個(gè)名字,是為了讓大家覺(jué)得直觀(guān),就像我們平常使用電腦上的USB插口插拔設(shè)備一樣.OK!下面我們來(lái)測(cè)試我們的“電腦”和“移動(dòng)存儲(chǔ)設(shè)備〃是否工作正常.我是用的C#控制臺(tái)程序,具體代碼如下:1namespaceInterfaceExample2臼國(guó){32臼國(guó){34弱56百申789{staticvoidMain(string口args){Computercomputer=newComputer();IMobileStoragemp3Player=newMP3Player();IMobileStorageflashDisk=newFlashDisk();1011121314151617rd1819202122o23242526272829newMobileHardDisk();IMobileStoragemobileHardDisknewMobileHardDisk();Console。WriteLine("IinsertedmyMP3Playerintomycomputerandcopysomemusictoit:");I computer.UsbDrive=mp3Player;I computer.WriteData();I Console.WriteLine();II Console。WriteLine(”Well,Ialsowanttocopyagreatmovietomycomputerfromamobilehadisk:");I computer.UsbDrive= mobileHardDisk;I computer。ReadData();I Console.WriteLine();II Console。WriteLine("OK!Ihavetoreadsomefilesfrommyflashdiskandcopyanotherfiletit:");I computer。UsbDrive=flashDisk;I computer.ReadData();I computer.WriteData();I Console。ReadLine();}}現(xiàn)在編譯、運(yùn)行程序,如果沒(méi)有問(wèn)題,將看到如下運(yùn)行結(jié)果:IinseFtedmyMP3Placerintomyconyuteyandcopy:somemusictoUritinjftcMP3Pla^er Uritf?finislied?WeIIj.Ia1st:umn七士心unpyagreatmovietomjfcomputerffghamaliileharddiskReadingfre)mK)ohzLleHai'dDzLslf 機(jī)由HtifiisliedfOKfIliavetcre^dfonefile?fronmyfl曰與力HUmndcupyanotherfilf?tcit■headingfr£)(nFlasliDislt 外好adfinished?WritingtoFlasliDisk Writefinistied*好的,看來(lái)我們的系統(tǒng)工作良好。

后來(lái)剛過(guò)了一個(gè)星期,就有人送來(lái)了新的移動(dòng)存儲(chǔ)設(shè)備N(xiāo)ewMobileStorage,讓我測(cè)試能不能用,我微微一笑,心想這不是小菜一碟,讓我們看看面向接口編程的威力吧!將測(cè)試程序修改成如下:1namespaceInterfaceExample2臼

溫馨提示

  • 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶(hù)所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶(hù)上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶(hù)上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶(hù)因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

評(píng)論

0/150

提交評(píng)論