




版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡介
接口與接口設(shè)計(jì)原則一.11種設(shè)計(jì)原則.單一職責(zé)原則一SingleResponsibilityPrinciple(SRP)就一個(gè)類而言,應(yīng)該僅有一個(gè)引起它變化的原因.職責(zé)即為“變化的原因〃.2。開放-封閉原則一OpenClosePrinciple(OCP)軟件實(shí)體(類、模塊、函數(shù)等)應(yīng)該是可以擴(kuò)展的,但是不可修改.對于擴(kuò)展是開放的,對于更改是封閉的。關(guān)鍵是抽象。將一個(gè)功能的通用部分和實(shí)現(xiàn)細(xì)節(jié)部分清晰的分離開來。開發(fā)人員應(yīng)該僅僅對程序中呈現(xiàn)出頻繁變化的那些部分作出抽象.拒絕不成熟的抽象和抽象本身一樣重要)3.里氏替換原則-LiskovSubstitutionPrinciple(LSP)子類型(subclass)必須能夠替換掉它們的基類型(superclass)。4。依賴倒置原則(IoCP)或依賴注入原則-DependenceInversionPrinciple(DIP)抽象不應(yīng)該依賴于細(xì)節(jié).細(xì)節(jié)應(yīng)該依賴于抽象.Hollywood原則:”Don'tcallus,we'llcallyou".程序中所有的依賴關(guān)系都應(yīng)該終止于抽象類和接口。針對接口而非實(shí)現(xiàn)編程.任何變量都不應(yīng)該持有一個(gè)指向具體類的指針或引用。任何類都不應(yīng)該從具體類派生。任何方法都不應(yīng)該覆寫他的任何基類中的已經(jīng)實(shí)現(xiàn)了的方法。5.接口隔離原則(ISP)不應(yīng)該強(qiáng)迫客戶依賴于它們不用的方法。接口屬于客戶,不屬于它所在的類層次結(jié)構(gòu).多個(gè)面向特定用戶的接口勝于一個(gè)通用接口。6。重用發(fā)布等價(jià)原則(REP)重用的粒度就是發(fā)布的粒度。7。共同封閉原則(CCP)包(類庫、DLL)中的所有類對于同一類性質(zhì)的變化應(yīng)該是共同封閉的。一個(gè)變化若對一個(gè)包產(chǎn)生影響,則將對該包中的所有類產(chǎn)生影響,而對于其他的包不造成任何影響..共同重用原則(CRP)一個(gè)包(類庫、DLL)中的所有類應(yīng)該是共同重用的。如果重用了包(類庫、DLL)中的一個(gè)類,那么就要重用包(類庫、DLL)中的所有類。(相互之間沒有緊密聯(lián)系的類不應(yīng)該在同一個(gè)包(類庫、DLL)中。)包(類庫、DLL)耦合原則.無環(huán)依賴原則(ADP)在包的依賴關(guān)系圖中不允許存在環(huán)。10。穩(wěn)定依賴原則(SDP)朝著穩(wěn)定的方向進(jìn)行依賴.應(yīng)該把封裝系統(tǒng)高層設(shè)計(jì)的軟件(比如抽象類)放進(jìn)穩(wěn)定的包中,不穩(wěn)定的包中應(yīng)該只包含那些很可能會改變的軟件(比如具體類)。11。穩(wěn)定抽象原則(SAP)包的抽象程度應(yīng)該和其穩(wěn)定程度一致。一個(gè)穩(wěn)定的包應(yīng)該也是抽象的,一個(gè)不穩(wěn)定的包應(yīng)該是抽象的.其它擴(kuò)展原則.BBP(BlackBoxPrinciple)黑盒原則多用類的聚合,少用類的繼承。13.DAP(DefaultAbstractionPrinciple)缺省抽象原則在接口和實(shí)現(xiàn)接口的類之間引入一個(gè)抽象類,這個(gè)類實(shí)現(xiàn)了接口的大部分操作.14。IDP(InterfaceDesignPrinciple)接口設(shè)計(jì)原則規(guī)劃一個(gè)接口而不是實(shí)現(xiàn)一個(gè)接口。15.DCSP(Don'tConcreteSupperclassPrinciple)不要構(gòu)造具體的超類原則,避免維護(hù)具體的超類。16。迪米特法則一個(gè)類只依賴其觸手可得的類。二.類的設(shè)計(jì)原則1。開閉原則Softwareentities(classes,modules,function,etc.)shouldbeopenforextension,butclosedformodification。軟件實(shí)體(模塊,類,方法等)應(yīng)該對擴(kuò)展開放,對修改關(guān)閉.開閉原則(OCP:Open—ClosedPrinciple)是指在進(jìn)行面向?qū)ο笤O(shè)計(jì)(OOD:ObjectOrientedDesign)中,設(shè)計(jì)類或其他程序單位時(shí),應(yīng)該遵循:-對擴(kuò)展開放(open)—對修改關(guān)閉(closed)的設(shè)計(jì)原則.開閉原則是判斷面向?qū)ο笤O(shè)計(jì)是否正確的最基本的原理之一。根據(jù)開閉原則,在設(shè)計(jì)一個(gè)軟件系統(tǒng)模塊(類,方法)的時(shí)候,應(yīng)該可以在不修改原有的模塊(修改關(guān)閉)的基礎(chǔ)上,能擴(kuò)展其功能(擴(kuò)展開放)。一擴(kuò)展開放:某模塊的功能是可擴(kuò)展的,則該模塊是擴(kuò)展開放的.軟件系統(tǒng)的功能上的可擴(kuò)展性要求模塊是擴(kuò)展開放的。修改關(guān)閉:某模塊被其他模塊調(diào)用,如果該模塊的源代碼不允許修改,則該模塊修改關(guān)閉的。軟件系統(tǒng)的功能上的穩(wěn)定性,持續(xù)性要求是修改關(guān)閉的.這也是系統(tǒng)設(shè)計(jì)需要遵循開閉原則的原因:1)穩(wěn)定性。開閉原則要求擴(kuò)展功能不修改原來的代碼,這可以讓軟件系統(tǒng)在變化中保持穩(wěn)定。2)擴(kuò)展性。開閉原則要求對擴(kuò)展開放,通過擴(kuò)展提供新的或改變原有的功能,讓軟件系統(tǒng)具有靈活的可擴(kuò)展性。遵循開閉原則的系統(tǒng)設(shè)計(jì),可以讓軟件系統(tǒng)可復(fù)用,并且易于維護(hù)。開閉原則的實(shí)現(xiàn)方法為了滿足開閉原則的對修改關(guān)閉(closedformodification)原則以及擴(kuò)展開放(openforextension)原則,應(yīng)該對軟件系統(tǒng)中的不變的部分加以抽象,在面向?qū)ο蟮脑O(shè)計(jì)中,可以把這些不變的部分加以抽象成不變的接口,這些不變的接口可以應(yīng)對未來的擴(kuò)展;-接口的最小功能設(shè)計(jì)原則。根據(jù)這個(gè)原則,原有的接口要么可以應(yīng)對未來的擴(kuò)展;不足的部分可以通過定義新的接口來實(shí)現(xiàn);模塊之間的調(diào)用通過抽象接口進(jìn)行,這樣即使實(shí)現(xiàn)層發(fā)生變化,也無需修改調(diào)用方的代碼。接口可以被復(fù)用,但接口的實(shí)現(xiàn)卻不一定能被復(fù)用.接口是穩(wěn)定的,關(guān)閉的,但接口的實(shí)現(xiàn)是可變的,開放的。可以通過對接口的不同實(shí)現(xiàn)以及類的繼承行為等為系統(tǒng)增加新的或改變系統(tǒng)原來的功能,實(shí)現(xiàn)軟件系統(tǒng)的柔軟擴(kuò)展。簡單地說,軟件系統(tǒng)是否有良好的接口(抽象)設(shè)計(jì)是判斷軟件系統(tǒng)是否滿足開閉原則的一種重要的判斷基準(zhǔn)?,F(xiàn)在多把開閉原則等同于面向接口的軟件設(shè)計(jì)。開閉原則的相對性軟件系統(tǒng)的構(gòu)建是一個(gè)需要不斷重構(gòu)的過程,在這個(gè)過程中,模塊的功能抽象,模塊與模塊間的關(guān)系,都不會從一開始就非常清晰明了,所以構(gòu)建100%滿足開閉原則的軟件系統(tǒng)是相當(dāng)困難的,這就是開閉原則的相對性。但在設(shè)計(jì)過程中,通過對模塊功能的抽象(接口定義),模塊之間的關(guān)系的抽象(通過接口調(diào)用),抽象與實(shí)現(xiàn)的分離(面向接口的程序設(shè)計(jì))等,可以盡量接近滿足開閉原則。2。單一職責(zé)原則前百RobertC。Martin氏為我們總結(jié)了在面向?qū)ο蟮脑O(shè)計(jì)(00D)中應(yīng)該遵循的原則,這些原則被稱為“PrinciplesofOOD〃,關(guān)于“Principlesof00D”的相關(guān)文章可以從ObjectMenter得到。本文介紹“Principlesof00D〃中的單一職責(zé)原則:SingleResponsibilityPrinciple(SRP).可以從這里查看SingleResponsibilityPrinciple(SRP)的原文.概要Thereshouldneverbemorethanonereasonforaclasstochange.永遠(yuǎn)不要讓一個(gè)類存在多個(gè)改變的理由。換句話說,如果一個(gè)類需要改變,改變它的理由永遠(yuǎn)只有一個(gè)。如果存在多個(gè)改變它的理由,就需要重新設(shè)計(jì)該類。SRP(SingleResponsibilityPrinciple)原則的核心含意是:只能讓一個(gè)類有且僅有一個(gè)職責(zé)。這也是單一職責(zé)原則的命名含義.為什么一個(gè)類不能有多于一個(gè)以上的職責(zé)呢?如果一個(gè)類具有一個(gè)以上的職責(zé),那么就會有多個(gè)不同的原因引起該類變化,而這種變化將影響到該類不同職責(zé)的使用者(不同用戶):1,一方面,如果一個(gè)職責(zé)使用了外部類庫,則使用另外一個(gè)職責(zé)的用戶卻也不得不包含這個(gè)未被使用的外部類庫。2,另一方面,某個(gè)用戶由于某個(gè)原因需要修改其中一個(gè)職責(zé),另外一個(gè)職責(zé)的用戶也將受到影響,他將不得不重新編譯和配置.這違反了設(shè)計(jì)的開閉原則,也不是我們所期望的。職責(zé)的劃分既然一個(gè)類不能有多個(gè)職責(zé),那么怎么劃分職責(zé)呢?Robert.CMartin給出了一個(gè)著名的定義:所謂一個(gè)類的一個(gè)職責(zé)是指引起該類變化的一個(gè)原因。Ifyoucanthinkofmorethanonemotiveforchangingaclass,thenthatclasshasmorethanoneresponsibility。如果你能想到一個(gè)類存在多個(gè)使其改變的原因,那么這個(gè)類就存在多個(gè)職責(zé).SingleResponsibilityPrinciple(SRP)的原文里舉了一個(gè)Modem的例子來說明怎么樣進(jìn)行職責(zé)的劃分,這里我們也沿用這個(gè)例子來說明一下:SRP違反例:publicinterfaceModem{〃撥號publicvoiddial(Stringpno);〃撥號〃掛斷〃發(fā)送數(shù)據(jù)〃掛斷〃發(fā)送數(shù)據(jù)〃接收數(shù)據(jù))咋一看,這是一個(gè)沒有任何問題的接口設(shè)計(jì)。但事實(shí)上,這個(gè)接口包含了2個(gè)職責(zé):第一個(gè)是連接管理(dial,hangup);另一個(gè)是數(shù)據(jù)通信(send,recv)。很多情況下,這2個(gè)職責(zé)沒有任何共通的部分,它們因?yàn)椴煌睦碛啥淖?,被不同部分的程序調(diào)用。所以它違反了SRP原則。下面的類圖將它的2個(gè)不同職責(zé)分成2個(gè)不同的接口,這樣至少可以讓客戶端應(yīng)用程序使用具有單一職責(zé)的接口:GGonnection。diaKpno:String):vnidGGonnection。diaKpno:String):vnid呂hangup^);MoidDataOhannele?sendCch:charji:void?chsr接口定義0Mademlmpiementation 具體類實(shí)現(xiàn)讓Modemimplementation實(shí)現(xiàn)這兩個(gè)接口。我們注意到,Modemimplementation又組合了2個(gè)職責(zé),這不是我們希望的,但有時(shí)這又是必須的。通常由于某些原因,迫使我們不得不綁定多個(gè)職責(zé)到一個(gè)類中,但我們至少可以通過接口的分割來分離應(yīng)用程序關(guān)心的概念。事實(shí)上,這個(gè)例子一個(gè)更好的設(shè)計(jì)應(yīng)該是這樣的,如圖:QConnection。diaftpno:String):voidQConnection。diaftpno:String):voidADataChannel&send(-ph:charXvoidone已江上char接口定義?ModemConnectionO?ModemConnectionOModemDataChanneI具體類實(shí)現(xiàn)小結(jié)SingleResponsibilityPrinciple(SRP)從職責(zé)(改變理由)的側(cè)面上為我們對類(接口)的抽象的顆粒度建立了判斷基準(zhǔn):在為系統(tǒng)設(shè)計(jì)類(接口)的時(shí)候應(yīng)該保證它們的單一職責(zé)性.3接口分隔原則前百RobertC。Martin氏為我們總結(jié)了在面向?qū)ο蟮脑O(shè)計(jì)(OOD)中應(yīng)該遵循的原則,這些原則被稱為“PrinciplesofOOD”,關(guān)于“"PrinciplesofOOD”的相關(guān)文章可以從ObjectMenter得到。本文介紹“PrinciplesofOOD”中的接口分隔原則:InterfaceSegregationPrinciple(ISP)??梢詮倪@里查看InterfaceSegregationPrinciple(ISP)的原文.概要Clientsshouldnotbeforcedtodependuponinterfacesthattheydonotuse.不能強(qiáng)迫用戶去依賴那些他們不使用的接口。換句話說,使用多個(gè)專門的接口比使用單一的總接口總要好。它包含了2層意思:一接口的設(shè)計(jì)原則:接口的設(shè)計(jì)應(yīng)該遵循最小接口原則,不要把用戶不使用的方法塞進(jìn)同一個(gè)接口里。如果一個(gè)接口的方法沒有被使用到,則說明該接口過胖,應(yīng)該將其分割成幾個(gè)功能專一的接口.一接口的依賴(繼承)原則:如果一個(gè)接口a依賴(繼承)另一個(gè)接口,則接口a相當(dāng)于繼承了接口6的方法,那么繼承了接口b后的接口a也應(yīng)該遵循上述原則:不應(yīng)該包含用戶不使用的方法。反之,則說明接口a被b給污染了,應(yīng)該重新設(shè)計(jì)它們的關(guān)系。如果用戶被迫依賴他們不使用的接口,當(dāng)接口發(fā)生改變時(shí),他們也不得不跟著改變.換而言之,一個(gè)用戶依賴了未使用但被其他用戶使用的接口,當(dāng)其他用戶修改該接口時(shí),依賴該接口的所有用戶都將受到影響。這顯然違反了開閉原則,也不是我們所期望的。下面我們舉例說明怎么設(shè)計(jì)接口或類之間的關(guān)系,使其不違反ISP原則。假如有一個(gè)Door,有l(wèi)ock,unlock功能,另外,可以在Door上安裝一個(gè)Alarm而使其具有報(bào)警功能.用戶可以選擇一般的Door,也可以選擇具有報(bào)警功能的Door。有以下幾種設(shè)計(jì)方法:ISP原則的違反例:方法一:在Door接口里定義所有的方法。圖:DoorolockO:void。unlockQ:voidQalarmO:uoid9CommonDoor@AlarmDoor?lo&k(^voidQunlock.0:void@alarmO:vqidoIockQjvoidQunlcic/):void&ahrmO:void但這樣一來,依賴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跟方法一一樣,依賴Door接口的CommonDoor卻不得不實(shí)現(xiàn)未使用的alarm()方法.違反了ISP原則.遵循ISP原則的例:方法三:通過多重繼承實(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方法。接口之間無繼承關(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ì)原則。方法四:通過委讓實(shí)現(xiàn)Door&Door&IockQ:void9unlock.0:vuid2G周三「mGalarm?voidAdAd日就日rDesignP日tt日rnjcommonDoor.IockG);匚口mmucommonDoor.IockG);匚口mmu門口口口匚un1口?kQ*J這種方法其實(shí)是委讓方式的Adapter設(shè)計(jì)模式的實(shí)現(xiàn)。在這種方法里,AlarmDoor實(shí)現(xiàn)了Alarm接口,同時(shí)把功能lock和unlock委讓給CommonDoor對象完成。這種設(shè)計(jì)遵循了ISP設(shè)計(jì)原則。小結(jié)InterfaceSegregationPrinciple(ISP)從對接口的使用上為我們對接口抽象的顆粒度建立了判斷基準(zhǔn):在為系統(tǒng)設(shè)計(jì)接口的時(shí)候,使用多個(gè)專門的接口代替單一的胖接口.4依賴倒置原則RobertC.Martin氏為我們總結(jié)了在面向?qū)ο蟮脑O(shè)計(jì)(OOD)中應(yīng)該遵循的原則,這些原則被稱為“PrinciplesofOOD",關(guān)于“PrinciplesofOOD”的相關(guān)文章可以從ObjectMenter得到。本文介紹DIP:DependencyInversionPrinciple-依賴倒置原則。有關(guān)DependencyInversionPrinciple(DIP)原文可以從這里即得到.該文提出了依賴倒置原則的2個(gè)重要方針:Highlevelmodulesshouldnotdependuponlowlevelmodules.Bothshoulddependuponabstractions.Abstractionsshouldnotdependupondetails.Detailsshoulddependuponabstractions。中文意思為:A.高層模塊不應(yīng)該依賴于低層模塊,二者都應(yīng)該依賴于抽象B.抽象不應(yīng)該依賴于細(xì)節(jié),細(xì)節(jié)應(yīng)該依賴于抽象概念解說:依賴:在程序設(shè)計(jì)中,如果一個(gè)模塊a使用/調(diào)用了另一個(gè)模塊b,我們稱模塊a依賴模塊b.高層模塊與低層模塊:往往在一個(gè)應(yīng)用程序中,我們有一些低層次的類,這些類實(shí)現(xiàn)了一些基本的或初級的操作,我們稱之為低層模塊;另外有一些高層次的類,這些類封裝了某些復(fù)雜的邏輯,并且依賴于低層次的類,這些類我們稱之為高層模塊.為什么叫做依賴倒置(DependencyInversion)呢?面向?qū)ο蟪绦蛟O(shè)計(jì)相對于面向過程(結(jié)構(gòu)化)程序設(shè)計(jì)而言,依賴關(guān)系被倒置了。因?yàn)閭鹘y(tǒng)的結(jié)構(gòu)化程序設(shè)計(jì)中,高層模塊總是依賴于低層模塊。問題的提出:RobertC。Martin氏在原文中給出了“BadDesign”的定義:Itishardtochangebecauseeverychangeaffectstoomanyotherpartsofthesystem。(Rigidity)系統(tǒng)很難改變,因?yàn)槊總€(gè)改變都會影響其他很多部分.Whenyoumakeachange,unexpectedpartsofthesystembreak。(Fragility)當(dāng)你對某地方做一修改,系統(tǒng)的看似無關(guān)的其他部分都不工作了。Itishardtoreuseinanotherapplicationbecauseitcannotbedisentangledfromthecurrentapplication.(Immobility)系統(tǒng)很難被另外一個(gè)應(yīng)用重用,因?yàn)槟愫茈y將要重用的部分從系統(tǒng)中分離開來.導(dǎo)致“BadDesign〃的很大原因是“高層模塊”過分依賴“低層模塊”。一個(gè)良好的設(shè)計(jì)應(yīng)該是系統(tǒng)的每一部分都是可替換的。如果“高層模塊〃過分依賴“低層模塊〃,一方面一旦“低層模塊”需要替換或者修改,“高層模塊”將受到影響;另一方面,高層模塊很難可以重用。比如,一個(gè)Copy模塊,需要把來自Keyboard的輸入復(fù)制到Print,即使對Keyboard和Print的封裝已經(jīng)做得非常好,但如果Copy模塊里直接使用Keyboard與Print,Copy任很難被其他應(yīng)用環(huán)境(比如需要輸出到磁盤時(shí))重用.問題的解決:為了解決上述問題,RobertC。Martin氏提出了OO設(shè)計(jì)的DependencyInversionPrinciple(DIP)原則。DIP給出了一個(gè)解決方案:在高層模塊與低層模塊之間,引入一個(gè)抽象接口層.HighLevelClasses(高層模塊) 〉A(chǔ)bstractionLayer(抽象接口層)-->LowLevelClasses(低層模塊)抽象接口是對低層模塊的抽象,低層模塊繼承或?qū)崿F(xiàn)該抽象接口。這樣,高層模塊不直接依賴低層模塊,高層模塊與低層模塊都依賴抽象接口層。當(dāng)然,抽象也不依賴低層模塊的實(shí)現(xiàn)細(xì)節(jié),低層模塊依賴(繼承或?qū)崿F(xiàn))抽象定義。RobertC。Martin氏給出的DIP方案的類的結(jié)構(gòu)圖:PolicyLayer 〉MechanismInterface(abstract) MechanismLayer 〉UtilityInterface(abstract) UtilityLayer類與類之間都通過AbstractLayer來組合關(guān)系。5里氏替換原則RobertCoMartin氏為我們總結(jié)了在面向?qū)ο蟮脑O(shè)計(jì)(OOD)中應(yīng)該遵循的原則,這些原則被稱為“PrinciplesofOOD”,關(guān)于“PrinciplesofOOD”的相關(guān)文章可以從ObjectMenter得到。本文介紹“PrinciplesofOOD〃中的里氏替換原則:LiskovSubstitutionPrinciple(LSP)??梢詮倪@里查看LiskovSubstitutionPrinciple(LSP)的原文國里氏替換原則LSP的概念解說Functionsthatusepointersorreferencestobaseclassesmustbeabletouseobjectsofderivedclasseswithoutknowingit。所有引用基類的地方必須能透明地使用其子類的對象。也就是說,只有滿足以下2個(gè)條件的OO設(shè)計(jì)才可被認(rèn)為是滿足了LSP原則:一不應(yīng)該在代碼中出現(xiàn)if/else之類對子類類型進(jìn)行判斷的條件。以下代碼就違反了LSP定義。if(objtypeofClassi){dosomething}elseif(objtypeofClass2){dosomethingelse)-子類應(yīng)當(dāng)可以替換父類并出現(xiàn)在父類能夠出現(xiàn)的任何地方,或者說如果我們把代碼中使用基類的地方用它的子類所代替,代碼還能正常工作.里氏替換原則LSP是使代碼符合開閉原則的一個(gè)重要保證。同時(shí)LSP體現(xiàn)了:一類的繼承原則:如果一個(gè)繼承類的對象可能會在基類出現(xiàn)的地方出現(xiàn)運(yùn)行錯(cuò)誤,則該子類不應(yīng)該從該基類繼承,或者說,應(yīng)該重新設(shè)計(jì)它們之間的關(guān)系。-動作正確性保證:從另一個(gè)側(cè)面上保證了符合LSP設(shè)計(jì)原則的類的擴(kuò)展不會給已有的系統(tǒng)引入新的錯(cuò)誤。類的繼承原則:RobertC。Martin氏在介紹LiskovSubstitutionPrinciple(LSP)的原文里,舉了Rectangle和Square的例子。這里沿用這個(gè)例子,但用Java語言對其加以重寫,并忽略了某些細(xì)節(jié)只列出下面的精要部分來說明里氏替換原則對類的繼承上的約束.代碼: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是基類,Square從Rectangle繼承。這種繼承關(guān)系有什么問題嗎?假如已有的系統(tǒng)中存在以下既有的業(yè)務(wù)邏輯代碼:voidg(Rectangler){r。setWidth(5);r。setHeight⑷;if(r.getWidth()*r.getHeight()!=20) {thrownewRuntimeException();})則對應(yīng)于擴(kuò)展類Square,在調(diào)用既有業(yè)務(wù)邏輯時(shí):Rectanglesquare=newSquare();g(square);時(shí)會拋出一個(gè)RuntimeException異常。這顯然違反了LSP原則.動作正確性保證:因?yàn)長SP對子類的約束,所以為已存在的類做擴(kuò)展構(gòu)造一個(gè)新的子類時(shí),根據(jù)LSP的定義,不會給已有的系統(tǒng)引入新的錯(cuò)誤.DesignbyContract根據(jù)BertrandMeyer氏提出的DesignbyContract(DBC:基于合同的設(shè)計(jì))概念的描述,對于類的一個(gè)方法,都有一個(gè)前提條件以及一個(gè)后續(xù)條件,前提條件說明方法接受什么樣的參數(shù)數(shù)據(jù)等,只有前提條件得到滿足時(shí),這個(gè)方法才能被調(diào)用;同時(shí)后續(xù)條件用來說明這個(gè)方法完成時(shí)的狀態(tài),如果一個(gè)方法的執(zhí)行會導(dǎo)致這個(gè)方法的后續(xù)條件不成立,那么這個(gè)方法也不應(yīng)該正常返回。現(xiàn)在把前提條件以及后續(xù)條件應(yīng)用到繼承子類中,子類方法應(yīng)該滿足:1)前提條件不強(qiáng)于基類.2)后續(xù)條件不弱于基類.換句話說,通過基類的接口調(diào)用一個(gè)對象時(shí),用戶只知道基類前提條件以及后續(xù)條件。因此繼承類不得要求用戶提供比基類方法要求的更強(qiáng)的前提條件,亦即,繼承類方法必須接受任何基類方法能接受的任何條件(參數(shù)).同樣,繼承類必須順從基類的所有后續(xù)條件,亦即,繼承類方法的行為和輸出不得違反由基類建立起來的任何約束,不能讓用戶對繼承類方法的輸出感到困惑。這樣,我們就有了基于合同的LSP,基于合同的LSP是LSP的一種強(qiáng)化。在很多情況下,在設(shè)計(jì)初期我們類之間的關(guān)系不是很明確,LSP則給了我們一個(gè)判斷和設(shè)計(jì)類之間關(guān)系的基準(zhǔn):需不需要繼承,以及怎樣設(shè)計(jì)繼承關(guān)系。三,針對接口編程,而不是針對實(shí)現(xiàn)編程在面向?qū)ο笤O(shè)計(jì)方法中有很多值得提倡的方法,這些方法可以為我們的設(shè)計(jì)帶來很大的靈活性,可復(fù)用性。其中一個(gè)原則就是“針對接口編程,而不是針對實(shí)現(xiàn)編程”這個(gè)原則帶來的好處有以下幾點(diǎn):Client不必知道其使用對象的具體所屬類。Client無需知道特定類,只需知道他們所期望的接口。一個(gè)對象可以很容易地被(實(shí)現(xiàn)了相同接口的)的另一個(gè)對象所替換.對象間的連接不必硬綁定(hardwire)到一個(gè)具體類的對象上,因此增加了靈活性。松散藕合(loosens coupling)。增加了重用的可能性。提高了(對象)組合的機(jī)率,因?yàn)楸话瑢ο罂梢允侨魏螌?shí)現(xiàn)了一個(gè)指定接口的類。但從辯證法的角度看,事物總有利有弊.“針對接口編程〃有如上諸多好處,卻不可避免的帶來設(shè)計(jì)的復(fù)雜性.特別對于沒有豐富經(jīng)驗(yàn)的設(shè)計(jì)人員。其中令我比較困惑的地方是:要想針對接口編程,就必然要最大化接口類,使包括所有子類的方法,這樣我們才能利用多態(tài)性用接口類來實(shí)現(xiàn)操作子類。但這會帶來以下幾點(diǎn)不足.違反面向?qū)ο蟮牧硪粋€(gè)原則,這個(gè)原則是:一個(gè)類只能定義那些對它的子類有意義的操作。接口類包括了并不是對每一個(gè)子類都有意義的方法,使接口類臃腫,難以理解。從父類繼承的無用方法,如何處理。敏捷開發(fā)提倡簡單設(shè)計(jì)的實(shí)踐,”并在實(shí)現(xiàn)新需求時(shí)抓住機(jī)會改進(jìn)設(shè)計(jì)”以對同類性質(zhì)的改動封閉,做到由需求的變化驅(qū)動設(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í)做出必要的抽象來滿足ocp原則等,或是在需求變動時(shí)確定系統(tǒng)所需的抽象(所需的封閉),當(dāng)然應(yīng)及早的刺激這種變化的出現(xiàn)(如測試驅(qū)動的開發(fā)方法)。OOD承諾了一系列的好處(靈活性可重用性可維護(hù)性),用OO語言設(shè)計(jì)開發(fā),若要方便的得到這些所謂的好處,有一系列的原則是要遵循的,如SRP,OCP,LSP,ISP等.SRP(單一職責(zé)原則)維護(hù)類的簡單性,類不應(yīng)承擔(dān)一個(gè)以上令其變化的原因,否則應(yīng)考慮分離并重新構(gòu)造類,但如果的應(yīng)用的變化方式總是導(dǎo)致類中的職責(zé)同時(shí)變化,卻沒必要分離他們Ocp(開閉原則)使OO系統(tǒng)做到對擴(kuò)展開放,對修改封閉。OCP的遵循關(guān)鍵在于抽象,其主要實(shí)現(xiàn)方式有:定義接口描繪所需的操作,client只需關(guān)注接口的調(diào)用,子類型可以以任何其選擇的方式實(shí)現(xiàn)接口,即所謂的stategy模式,或者定義抽象類并于其中實(shí)現(xiàn)公共操作,個(gè)性操作定義為abstract或virtual,由子類型負(fù)責(zé)個(gè)性化實(shí)現(xiàn),通過此兩法,將功能的通用部分和實(shí)現(xiàn)細(xì)節(jié)分離出來。當(dāng)然,設(shè)計(jì)人員應(yīng)該確定(猜測或憑經(jīng)驗(yàn))系統(tǒng)對哪種變化做到封閉,因?yàn)椴豢赡軐λ凶兓龅椒忾],如《敏捷模式實(shí)踐》中提到shape類型排序處理問題,為做到對排序安排(或變動)的封閉(使得各子類型間無需相互知曉,也可以做到自由安排排序順序),選擇使用“數(shù)據(jù)驅(qū)動”的方式(即單獨(dú)構(gòu)造結(jié)構(gòu)表示排序安排-其中以子類類型在結(jié)構(gòu)中的排列位置表先后),于shape基類中實(shí)現(xiàn)一次Precedes操作即可,子類型無需分別實(shí)現(xiàn).OCP作為OOD核心所在依賴抽象來實(shí)現(xiàn),但敏捷設(shè)計(jì)(或者說好的設(shè)計(jì))拒絕不成熟的抽象,程序僅應(yīng)對頻繁變化的部分做出抽象.Liskov替換原則是使得ocp成為可能的原則之一,強(qiáng)調(diào)“子類型subtype必須能夠替換掉它們的基類型basetype",控制OO的繼承關(guān)系安排,在OOD用is—a來確定類間的繼承關(guān)系,LSP指出這種is—a關(guān)系是就行為方式(即類的各操作)而言的,而行為方式是可以合理假設(shè)的,是客戶程序所依賴的。為遵循LSP,可借用DBC(designbycontract)的操作前置條件和后置條件,“要使操作得以執(zhí)行,前置條件必須為真,執(zhí)行完畢后,該操作要確保后置條件為真”(為每個(gè)方法注明其前置和后置條件十分有幫助),如此,則“在重新聲明派生類中操作時(shí),只能使用相等或更弱的前置條件來替換原始的前置條件,只能用相等或更強(qiáng)的后置條件來替換原始的后置條件"(interface和其實(shí)現(xiàn)類間抽象方法和其實(shí)現(xiàn)此二者一定滿足前述條件)。同時(shí)亦可用前述ocp遵循所用的二模式使設(shè)計(jì)符合LSP,另外子類型中的異常拋出應(yīng)考慮在遵循LSP的范圍內(nèi)。關(guān)于提取公共部分的設(shè)計(jì)工具:”提取公共職責(zé)放入超類中,稍后添加的新的子類型可能會以新的方式支持同樣的職責(zé),此時(shí)原來的超類可能會是一個(gè)抽象類”.DIP(依賴倒置原則),作為framework的設(shè)計(jì)核心,其相對于傳統(tǒng)軟件設(shè)計(jì)而言,通常(傳統(tǒng))軟件設(shè)計(jì)中采用結(jié)構(gòu)化設(shè)計(jì)用高層模塊直接調(diào)用底層模塊,這樣高層模塊將嚴(yán)重依賴于底層模塊的變動,在OOD中通過為高層模塊定義所需使用的服務(wù)接口,底層模塊現(xiàn)實(shí)這樣的接口,高層模塊通過抽象接口使用下一層(strategy模式所聲明的),如此看來接口的擁有者一般是其使用者而非其實(shí)現(xiàn)者。通常為了滿足DIP一良好OOD的基本底層機(jī)制,我么需要找出系統(tǒng)中潛在的抽象,而抽象通常是那些不隨具體細(xì)節(jié)變化而變化的東東。ISP接口隔離原則,如SRP維護(hù)類的簡單性一樣,ISP用于維護(hù)接口的簡單和必要性,因?yàn)榻涌谑菫榭蛻粽{(diào)用的,因此其應(yīng)該是“大小尺寸合適的”,“胖〃接口顯然對調(diào)用者造成累贅,ISP則用于將“胖”接口分離成多個(gè)合適的接口.當(dāng)然,在系統(tǒng)設(shè)計(jì)實(shí)現(xiàn)中要做到這些并非容易,單單知道其存在未必做得到將其實(shí)現(xiàn)到系統(tǒng)中,開發(fā)經(jīng)驗(yàn)的積累同樣重要,但早些知道存在個(gè)意識并在做時(shí)將其考慮進(jìn)去,也是積累,慢慢來吧,n多事要做吶。.面向接口編程詳解—-思想基礎(chǔ)我想,對于各位使用面向?qū)ο缶幊陶Z言的程序員來說,“接口”這個(gè)名詞一定不陌生,但是不知各位有沒有這樣的疑惑:接口有什么用途?它和抽象類有什么區(qū)別?能不能用抽象類代替接口呢?而且,作為程序員,一定經(jīng)常聽到“面向接口編程”這個(gè)短語,那么它是什么意思?有什么思想內(nèi)涵?和面向?qū)ο缶幊淌鞘裁搓P(guān)系?本文將一一解答這些疑問。1.面向接口編程和面向?qū)ο缶幊淌鞘裁搓P(guān)系首先,面向接口編程和面向?qū)ο缶幊滩⒉皇瞧郊壍?它并不是比面向?qū)ο缶幊谈冗M(jìn)的一種獨(dú)立的編程思想,而是附屬于面向?qū)ο笏枷塍w系,屬于其一部分?;蛘哒f,它是面向?qū)ο缶幊腆w系中的思想精髓之一。2。接口的本質(zhì)接口,在表面上是由幾個(gè)沒有主體代碼的方法定義組成的集合體,有唯一的名稱,可以被類或其他接口所實(shí)現(xiàn)(或者也可以說繼承)。它在形式上可能是如下的樣子:interfaceInterfaceName日國{voidMethod1();voidMethod2(intpara1);voidMethod3(stringpara2,stringpara3);那么,接口的本質(zhì)是什么呢?或者說接口存在的意義是什么。我認(rèn)為可以從以下兩個(gè)視角考慮:1)接口是一組規(guī)則的集合,它規(guī)定了實(shí)現(xiàn)本接口的類或接口必須擁有的一組規(guī)則.體現(xiàn)了自然界“如果你是……則必須能……”的理念。例如,在自然界中,人都能吃飯,即“如果你是人,則必須能吃飯〃。那么模擬到計(jì)算機(jī)程序中,就應(yīng)該有一個(gè)IPerson(習(xí)慣上,接口名由“I"開頭)接口,并有一個(gè)方法叫Eat(),然后我們規(guī)定,每一個(gè)表示“人”的類,必須實(shí)現(xiàn)IPerson接口,這就模擬了自然界“如果你是人,則必須能吃飯”這條規(guī)則。從這里,我想各位也能看到些許面向?qū)ο笏枷氲臇|西。面向?qū)ο笏枷氲暮诵闹唬褪悄M真實(shí)世界,把真實(shí)世界中的事物抽象成類,整個(gè)程序靠各個(gè)類的實(shí)例互相通信、互相協(xié)作完成系統(tǒng)功能,這非常符合真實(shí)世界的運(yùn)行狀況,也是面向?qū)ο笏枷氲木琛?)接口是在一定粒度視圖上同類事物的抽象表示。注意這里我強(qiáng)調(diào)了在一定粒度視圖上,因?yàn)椤巴愂挛铩边@個(gè)概念是相對的,它因?yàn)榱6纫晥D不同而不同。例如,在我的眼里,我是一個(gè)人,和一頭豬有本質(zhì)區(qū)別我可以接受我和我同學(xué)是同類這個(gè)說法,但絕不能接受我和一頭豬是同類。但是,如果在一個(gè)動物學(xué)家眼里,我和豬應(yīng)該是同類,因?yàn)槲覀兌际莿游铮梢哉J(rèn)為“人”和“豬”都實(shí)現(xiàn)了lAnimal這個(gè)接口,而他在研究動物行為時(shí),不會把我和豬分開對待,而會從“動物”這個(gè)較大的粒度上研究,但他會認(rèn)為我和一棵樹有本質(zhì)區(qū)別?,F(xiàn)在換了一個(gè)遺傳學(xué)家,情況又不同了,因?yàn)樯锒寄苓z傳,所以在他眼里我不僅和豬沒區(qū)別,和一只蚊子、一個(gè)細(xì)菌、一顆樹、一個(gè)蘑菇乃至一個(gè)SARS病毒都沒什么區(qū)別,因?yàn)樗麜J(rèn)為我們都實(shí)現(xiàn)了IDescendable這個(gè)接口(注:descendvi。遺傳),即我們都是可遺傳的東西,他不會分別研究我們,而會將所有生物作為同類進(jìn)行研究,在他眼里沒有人和病毒之分,只有可遺傳的物質(zhì)和不可遺傳的物質(zhì)。但至少,我和一塊石頭還是有區(qū)別的。可不幸的事情發(fā)生了,某日,地球上出現(xiàn)了一位偉大的人,他叫列寧,他在熟讀馬克思、恩格斯的辯證唯物主義思想巨著后頗有心得,于是他下了一個(gè)著名的定義:所謂物質(zhì),就是能被意識所反映的客觀實(shí)在。至此,我和一塊石頭、一絲空氣、一條成語和傳輸手機(jī)信號的電磁場已經(jīng)沒什么區(qū)別了,因?yàn)樵诹袑幍难劾铮覀兌际强梢员灰庾R所反映的客觀實(shí)在。如果列寧是一名程序員,他會這么說:所謂物質(zhì),就是所有同時(shí)實(shí)現(xiàn)了“IReflectabe”和“IEsse”兩個(gè)接口的類所生成的實(shí)例。(注:reflectv。反映essen.客觀實(shí)在)也許你會覺得我上面的例子像在瞎掰,但是,這正是接口得以存在的意義。面向?qū)ο笏枷牒秃诵闹唤凶龆鄳B(tài)性,什么叫多態(tài)性?說白了就是在某個(gè)粒度視圖層面上對同類事物不加區(qū)別的對待而統(tǒng)一處理.而之所以敢這樣做,就是因?yàn)橛薪涌诘拇嬖凇O衲莻€(gè)遺傳學(xué)家,他明白所有生物都實(shí)現(xiàn)了IDescendable接口,那只要是生物,一定有Descend()這個(gè)方法,于是他就可以統(tǒng)一研究,而不至于分別研究每一種生物而最終累死??赡苓@里還不能給你一個(gè)關(guān)于接口本質(zhì)和作用的直觀印象。那么在后文的例子和對幾個(gè)設(shè)計(jì)模式的解析中,你將會更直觀體驗(yàn)到接口的內(nèi)涵。3.面向接口編程綜述通過上文,我想大家對接口和接口的思想內(nèi)涵有了一個(gè)了解,那么什么是面向接口編程呢?我個(gè)人的定義是:在系統(tǒng)分析和架構(gòu)中,分清層次和依賴關(guān)系,每個(gè)層次不是直接向其上層提供服務(wù)(即不是直接實(shí)例化在上層中),而是通過定義一組接口,僅向上層暴露其接口功能,上層對于下層僅僅是接口依賴,而不依賴具體類。這樣做的好處是顯而易見的,首先對系統(tǒng)靈活性大有好處。當(dāng)下層需要改變時(shí),只要接口及接口功能不變,則上層不用做任何修改。甚至可以在不改動上層代碼時(shí)將下層整個(gè)替換掉,就像我們將一個(gè)WD的60G硬盤換成一個(gè)希捷的160G的硬盤,計(jì)算機(jī)其他地方不用做任何改動,而是把原硬盤拔下來、新硬盤插上就行了,因?yàn)橛?jì)算機(jī)其他部分不依賴具體硬盤而只依賴一個(gè)IDE接口,只要硬盤實(shí)現(xiàn)了這個(gè)接口,就可以替換上去。從這里看,程序中的接口和現(xiàn)實(shí)中的接口極為相似,所以我一直認(rèn)為,接口(interface)這個(gè)詞用的真是神似!使用接口的另一個(gè)好處就是不同部件或?qū)哟蔚拈_發(fā)人員可以并行開工,就像造硬盤的不用等造CPU的,也不用等造顯示器的,只要接口一致,設(shè)計(jì)合理,完全可以并行進(jìn)行開發(fā),從而提高效率.本篇文章先到這里。最后我想再啰嗦一句:面向?qū)ο蟮木枋悄M現(xiàn)實(shí),這也可以說是我這篇文章的靈魂。所以多從現(xiàn)實(shí)中思考面向?qū)ο蟮臇|西,對提高系統(tǒng)分析設(shè)計(jì)能力大有脾益。仔細(xì)看了各位的回復(fù),非常高興能和大家一起討論技術(shù)問題.感謝給出肯定的朋友,也要感謝提出意見和質(zhì)疑的朋友,這促使我更深入思考一些東西,希望能借此進(jìn)步.在這里我想補(bǔ)充一些東西,以討論一些回復(fù)中比較集中的問題。.關(guān)于“面向接口編程”中的“接口”與具體面向?qū)ο笳Z言中“接口”兩個(gè)詞看到有朋友提出“面向接口編程”中的“接口〃二字應(yīng)該比單純編程語言中的interface范圍更大.我經(jīng)過思考,覺得很有道理。這里我寫的確實(shí)不太合理。我想,面向?qū)ο笳Z言中的“接口”是指具體的一種代碼結(jié)構(gòu),例如C#中用interface關(guān)鍵字定義的接口.而“面向接口編程”中的“接口”可以說是一種從軟件架構(gòu)的角度、從一個(gè)更抽象的層面上指那種用于隱藏具體底層類和實(shí)現(xiàn)多態(tài)性的結(jié)構(gòu)部件。從這個(gè)意義上說,如果定義一個(gè)抽象類,并且目的是為了實(shí)現(xiàn)多態(tài),那么我認(rèn)為把這個(gè)抽象類也稱為“接口〃是合理的。但是用抽象類實(shí)現(xiàn)多態(tài)合理不合理?在下面第二條討論。概括來說,我覺得兩個(gè)“接口”的概念既相互區(qū)別又相互聯(lián)系。”面向接口編程”中的接口是一種思想層面的用于實(shí)現(xiàn)多態(tài)性、提高軟件靈活性和可維護(hù)性的架構(gòu)部件,而具體語言中的“接口”是將這種思想中的部件具體實(shí)施到代碼里的手段。.關(guān)于抽象類與接口看到回復(fù)中這是討論的比較激烈的一個(gè)問題。很抱歉我考慮不周沒有在文章中討論這個(gè)問題。我個(gè)人對這個(gè)問題的理解如下:如果單從具體代碼來看,對這兩個(gè)概念很容易模糊,甚至覺得接口就是多余的,因?yàn)閱螐木唧w功能來看,除多重繼承外(C#,Java中),抽象類似乎完全能取代接口.但是,難道接口的存在是為了實(shí)現(xiàn)多重繼承?當(dāng)然不是。我認(rèn)為,抽象類和接口的區(qū)別在于使用動機(jī).使用抽象類是為了代碼的復(fù)用,而使用接口的動機(jī)是為了實(shí)現(xiàn)多態(tài)性.所以,如果你在為某個(gè)地方該使用接口還是抽象類而猶豫不決時(shí),那么可以想想你的動機(jī)是什么.看到有朋友對IPerson這個(gè)接口的質(zhì)疑,我個(gè)人的理解是,IPerson這個(gè)接口該不該定義,關(guān)鍵看具體應(yīng)用中是怎么個(gè)情況。如果我們的項(xiàng)目中有Women和Man,都繼承Person,而且Women和Man絕大多數(shù)方法都相同,只有一個(gè)方法DoSomethingInWC()不同(例子比較粗俗,各位見諒),那么當(dāng)然定義一個(gè)AbstractPerson抽象類比較合理,因?yàn)樗梢园哑渌蟹椒ǘ及M(jìn)去,子類只定義DoSomethingInWC(),大大減少了重復(fù)代碼量。但是,如果我們程序中的Women和Man兩個(gè)類基本沒有共同代碼,而且有一個(gè)PersonHandle類需要實(shí)例化他們,并且不希望知道他們是男是女,而只需把他們當(dāng)作人看待,并實(shí)現(xiàn)多態(tài),那么定義成接口就有必要了??偠灾涌谂c抽象類的區(qū)別主要在于使用的動機(jī),而不在于其本身。而一個(gè)東西該定義成抽象類還是接口,要根據(jù)具體環(huán)境的上下文決定。再者,我認(rèn)為接口和抽象類的另一個(gè)區(qū)別在于,抽象類和它的子類之間應(yīng)該是一般和特殊的關(guān)系,而接口僅僅是它的子類應(yīng)該實(shí)現(xiàn)的一組規(guī)則。(當(dāng)然,有時(shí)也可能存在一般與特殊的關(guān)系,但我們使用接口的目的不在這里)如,交通工具定義成抽象類,汽車、飛機(jī)、輪船定義成子類,是可以接受的,因?yàn)槠?、飛機(jī)、輪船都是一種特殊的交通工具。再譬如Icomparable接口,它只是說,實(shí)現(xiàn)這個(gè)接口的類必須要可以進(jìn)行比較,這是一條規(guī)則。如果Car這個(gè)類實(shí)現(xiàn)了Ic。mparable,只是說,我們的Car中有一個(gè)方法可以對兩個(gè)Car的實(shí)例進(jìn)行比較,可能是比哪輛車更貴,也可能比哪輛車更大,這都無所謂,但我們不能說“汽車是一種特殊的可以比較”,這在文法上都不通。五.面向接口編程詳解問題的提出定義:現(xiàn)在我們要開發(fā)一個(gè)應(yīng)用,模擬移動存儲設(shè)備的讀寫,即計(jì)算機(jī)與U盤、MP3、移動硬盤等設(shè)備進(jìn)行數(shù)據(jù)交換。上下文(環(huán)境):已知要實(shí)現(xiàn)U盤、MP3播放器、移動硬盤三種移動存儲設(shè)備,要求計(jì)算機(jī)能同這三種設(shè)備進(jìn)行數(shù)據(jù)交換,并且以后可能會有新的第三方的移動存儲設(shè)備,所以計(jì)算機(jī)必須有擴(kuò)展性,能與目前未知而以后可能會出現(xiàn)的存儲設(shè)備進(jìn)行數(shù)據(jù)交換.各個(gè)存儲設(shè)備間讀、寫的實(shí)現(xiàn)方法不同,U盤和移動硬盤只有這兩個(gè)方法,MP3Player還有一個(gè)PlayMusic方法。名詞定義:數(shù)據(jù)交換={讀,寫}看到上面的問題,我想各位腦子中一定有了不少想法,這是個(gè)很好解決的問題,很多方案都能達(dá)到效果。下面,我列舉幾個(gè)典型的方案。解決方案列舉方案一:分別定義FlashDisk、MP3Player、MobileHardDisk三個(gè)類,實(shí)現(xiàn)各自的Read和Write方法。然后在Computer類中實(shí)例化上述三個(gè)類,為每個(gè)類分別寫讀、寫方法.例如,為FlashDisk寫ReadFromFlashDisk、WriteToFlashDisk兩個(gè)方法??偣擦鶄€(gè)方法。方案二:定義抽象類MobileStorage,在里面寫虛方法Read和Write,m個(gè)存儲設(shè)備繼承此抽象類,并重寫Read和Write方法。Computer類中包含一個(gè)類型為MobileStorage的成員變量,并為其編寫get/set器,這樣Computer中只需要兩個(gè)方法:ReadData和WriteData,并通過多態(tài)性實(shí)現(xiàn)不同移動設(shè)備的讀寫。方案三:與方案二基本相同,只是不定義抽象類,而是定義接口IMobileStorage,移動存儲器類實(shí)現(xiàn)此接口。Computer中通過依賴接口IMobileStorage實(shí)現(xiàn)多態(tài)性。方案四:定義接口IReadable和IWritable,兩個(gè)接口分別只包含Read和Write,然后定義接口IMobileStorage接口繼承自IReadable和IWritable,剩下的實(shí)現(xiàn)與方案三相同.下面,我們來分析一下以上四種方案:首先,方案一最直白,實(shí)現(xiàn)起來最簡單但是它有一個(gè)致命的弱點(diǎn):可擴(kuò)展性差?;蛘哒f,不符合“開放-關(guān)閉原則”(注:意為對擴(kuò)展開放,對修改關(guān)閉)。當(dāng)將來有了第三方擴(kuò)展移動存儲設(shè)備時(shí),必須對Computer進(jìn)行修改。這就如在一個(gè)真實(shí)的計(jì)算機(jī)上,為每一種移動存儲設(shè)備實(shí)現(xiàn)一個(gè)不同的插口、并分別有各自的驅(qū)動程序.當(dāng)有了一種新的移動存儲設(shè)備后,我們就要將計(jì)算機(jī)大卸八塊,然后增加一個(gè)新的插口,在編寫一套針對此新設(shè)備的驅(qū)動程序。這種設(shè)計(jì)顯然不可取.此方案的另一個(gè)缺點(diǎn)在于,冗余代碼多。如果有100種移動存儲,那我們的Computer中豈不是要至少寫200個(gè)方法,這是不能接受的!我們再來看方案二和方案三,之所以將這兩個(gè)方案放在一起討論,是因?yàn)樗麄兓臼且粋€(gè)方案(從思想層面上來說,只不過實(shí)現(xiàn)手段不同,一個(gè)是使用了抽象類,一個(gè)是使用了接口,而且最終達(dá)到的目的應(yīng)該是一樣的.我們先來評價(jià)這種方案:首先它解決了代碼冗余的問題,因?yàn)榭梢詣討B(tài)替換移動設(shè)備,并且都實(shí)現(xiàn)了共同的接口,所以不管有多少種移動設(shè)備,只要一個(gè)Read方法和一個(gè)Write方法,多態(tài)性就幫我們解決問題了.而對第一個(gè)問題,由于可以運(yùn)行時(shí)動態(tài)替換,而不必將移動存儲類硬編碼在Computer中,所以有了新的第三方設(shè)備,完全可以替換進(jìn)去運(yùn)行。這就是所謂的“依賴接口,而不是依賴與具體類”,不信你看看,Computer類只有一個(gè)MobileStorage類型或IMobileStorage類型的成員變量,至于這個(gè)變量具體是什么類型,它并不知道,這取決于我們在運(yùn)行時(shí)給這個(gè)變量的賦值。如此一來,Computer和移動存儲器類的耦合度大大下降。那么這里該選抽象類還是接口呢?還記得第一篇文章我對抽象類和接口選擇的建議嗎?看動機(jī).這里,我們的動機(jī)顯然是實(shí)現(xiàn)多態(tài)性而不是為了代碼復(fù)用,所以當(dāng)然要用接口。最后我們再來看一看方案四,它和方案三很類似,只是將“可讀”和“可寫〃兩個(gè)規(guī)則分別抽象成了接口,然后讓IMobileStorage再繼承它們。這樣做,顯然進(jìn)一步提高了靈活性,但是,這有沒有設(shè)計(jì)過度的嫌疑呢?我的觀點(diǎn)是:這要看具體情況。如果我們的應(yīng)用中可能會出現(xiàn)一些類,這些類只實(shí)現(xiàn)讀方法或只實(shí)現(xiàn)寫方法,如只讀光盤,那么這樣做也是可以的。如果我們知道以后出現(xiàn)的東西都是能讀又能寫的,那這兩個(gè)接口就沒有必要了.其實(shí)如果將只讀設(shè)備的Write方法留空或拋出異常,也可以不要這兩個(gè)接口。總之一句話:理論是死的,人是活的,一切從現(xiàn)實(shí)需要來,防止設(shè)計(jì)不足,也要防止設(shè)計(jì)過度。在這里,我們姑且認(rèn)為以后的移動存儲都是能讀又能寫的,所以我們選方案三.實(shí)現(xiàn)下面,我們要將解決方案加以實(shí)現(xiàn).我選擇的語言是C#,但是在代碼中不會用到C#特有的性質(zhì),所以使用其他語言的朋友一樣可以參考。首先編寫IMobileStorage接口:1namespaceInterfaceExample2日圖{3 publicinterfaceIMobileStorage4申申 {voidRead();〃從自身讀數(shù)據(jù)voidWrite();〃將數(shù)據(jù)寫入自身}}比較簡單,只有兩個(gè)方法,沒什么好說的,接下來是三個(gè)移動存儲設(shè)備的具體實(shí)現(xiàn)代碼:U盤1namespaceInterfaceExample2日叫34百申56百申789101112韓131415卜16卜17}publicclassFlashDisk:IMobileStorage{publicvoidRead(){Console。WriteLine("ReadingfromFlashDisk ")Console.WriteLine("Readfinished!”);}publicvoidWrite(){Console。WriteLine("WritingtoFlashDisk ");Console.WriteLine("Writefinished!”);))MP31namespaceInterfaceExample2臼國{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 ");)移動硬盤1namespaceInterfaceExample2臼國{3 publicclassMobileHardDisk:IMobileStorage肅申891011{publicvoidRead(){Console。WriteLine("ReadingfromMobileHardDiskConsole.WriteLine("Readfinished!”);}12中申131415卜16卜17}publicvoidWrite(){Console.WriteLine("WritingtoMobileHardDisk ");Console.WriteLine("Writefinished!");))F面,我們來寫Computer:可以看到,它們都實(shí)現(xiàn)YlMobileStorage接口,并重寫了各自不同的Read和F面,我們來寫Computer:1namespaceInterfaceExample2日國{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就是可替換的移動存儲設(shè)備,之所以用這個(gè)名字,是為了讓大家覺得直觀,就像我們平常使用電腦上的USB插口插拔設(shè)備一樣.OK!下面我們來測試我們的“電腦”和“移動存儲設(shè)備〃是否工作正常.我是用的C#控制臺程序,具體代碼如下:1namespaceInterfaceExample2臼國{32臼國{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)行程序,如果沒有問題,將看到如下運(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*好的,看來我們的系統(tǒng)工作良好。
后來剛過了一個(gè)星期,就有人送來了新的移動存儲設(shè)備NewMobileStorage,讓我測試能不能用,我微微一笑,心想這不是小菜一碟,讓我們看看面向接口編程的威力吧!將測試程序修改成如下:1namespaceInterfaceExample2臼
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 薪酬福利調(diào)整說明書與實(shí)施方案解讀
- 綠色能源供應(yīng)協(xié)議書
- 兄妹共同出資購房合同
- 全新綠化工程協(xié)議書
- 電力行業(yè)電力供應(yīng)穩(wěn)定性免責(zé)協(xié)議
- 員工年度工作總結(jié)與未來發(fā)展規(guī)劃報(bào)告
- 項(xiàng)目合作方案設(shè)計(jì)建議書
- 購買公司股份協(xié)議書十
- 第二單元 社會主義制度的建立與社會主義建設(shè)的探索 大單元教學(xué)設(shè)計(jì) 2023-2024學(xué)年部編版八年級歷史下冊
- 第二單元4《古詩三首》教學(xué)設(shè)計(jì)-2024-2025學(xué)年統(tǒng)編版語文三年級上冊
- 《物理學(xué)的發(fā)展史》課件
- 2025年廣東廣州市海珠區(qū)官洲街道辦事處政府雇員招聘5人高頻重點(diǎn)提升(共500題)附帶答案詳解
- 《道路交通安全法》課件完整版
- 《小腸梗阻的診斷與治療中國專家共識(2023版)》解讀
- 2024屆廣東省廣州市高三一??荚囉⒄Z試題講評課件
- 切削加工中的刀具路徑規(guī)劃算法考核試卷
- 《推拿學(xué)》期末考試復(fù)習(xí)題庫(含答案)
- 2024年經(jīng)濟(jì)師考試工商管理(中級)專業(yè)知識和實(shí)務(wù)試卷及解答參考
- 10kV配電室工程施工方案設(shè)計(jì)
- 2025年中國洗衣凝珠行業(yè)市場現(xiàn)狀及投資態(tài)勢分析報(bào)告(智研咨詢)
- DB41T 2466-2023 浸水電梯使用管理規(guī)范
評論
0/150
提交評論