不兼容结构的协调——适配器模式
本文轉(zhuǎn)載自 :http://blog.csdn.net/lovelion/article/details/8624325
我的筆記本電腦的工作電壓是20V,而我國(guó)的家庭用電是220V,如何讓20V的筆記本電腦能夠在220V的電壓下工作?答案是引入一個(gè)電源適配器(AC Adapter),俗稱充電器或變壓器,有了這個(gè)電源適配器,生活用電和筆記本電腦即可兼容,如圖9-1所示:
圖9-1?電源適配器示意圖
????? 在軟件開(kāi)發(fā)中,有時(shí)也存在類似這種不兼容的情況,我們也可以像引入一個(gè)電源適配器一樣引入一個(gè)稱之為適配器的角色來(lái)協(xié)調(diào)這些存在不兼容的結(jié)構(gòu),這種設(shè)計(jì)方案即為適配器模式。
?
9.1?沒(méi)有源碼的算法庫(kù)
| ?????? Sunny軟件公司在很久以前曾開(kāi)發(fā)了一個(gè)算法庫(kù),里面包含了一些常用的算法,例如排序算法和查找算法,在進(jìn)行各類軟件開(kāi)發(fā)時(shí)經(jīng)常需要重用該算法庫(kù)中的算法。在為某學(xué)校開(kāi)發(fā)教務(wù)管理系統(tǒng)時(shí),開(kāi)發(fā)人員發(fā)現(xiàn)需要對(duì)學(xué)生成績(jī)進(jìn)行排序和查找,該系統(tǒng)的設(shè)計(jì)人員已經(jīng)開(kāi)發(fā)了一個(gè)成績(jī)操作接口ScoreOperation,在該接口中聲明了排序方法sort(int[])?和查找方法search(int[], int),為了提高排序和查找的效率,開(kāi)發(fā)人員決定重用算法庫(kù)中的快速排序算法類QuickSort和二分查找算法類BinarySearch,其中QuickSort的quickSort(int[])方法實(shí)現(xiàn)了快速排序,BinarySearch?的binarySearch (int[], int)方法實(shí)現(xiàn)了二分查找。 ?????? 由于某些原因,現(xiàn)在Sunny公司開(kāi)發(fā)人員已經(jīng)找不到該算法庫(kù)的源代碼,無(wú)法直接通過(guò)復(fù)制和粘貼操作來(lái)重用其中的代碼;部分開(kāi)發(fā)人員已經(jīng)針對(duì)ScoreOperation接口編程,如果再要求對(duì)該接口進(jìn)行修改或要求大家直接使用QuickSort類和BinarySearch類將導(dǎo)致大量代碼需要修改。 ?????? Sunny軟件公司開(kāi)發(fā)人員面對(duì)這個(gè)沒(méi)有源碼的算法庫(kù),遇到一個(gè)幸福而又煩惱的問(wèn)題:如何在既不修改現(xiàn)有接口又不需要任何算法庫(kù)代碼的基礎(chǔ)上能夠?qū)崿F(xiàn)算法庫(kù)的重用? |
???????通過(guò)分析,我們不難得知,現(xiàn)在Sunny軟件公司面對(duì)的問(wèn)題有點(diǎn)類似本章最開(kāi)始所提到的電壓?jiǎn)栴},成績(jī)操作接口ScoreOperation好比只支持20V電壓的筆記本,而算法庫(kù)好比220V的家庭用電,這兩部分都沒(méi)有辦法再進(jìn)行修改,而且它們?cè)臼莾蓚€(gè)完全不相關(guān)的結(jié)構(gòu),如圖9-2所示:
圖9-2?需協(xié)調(diào)的兩個(gè)系統(tǒng)的結(jié)構(gòu)示意圖
?????? 現(xiàn)在我們需要ScoreOperation接口能夠和已有算法庫(kù)一起工作,讓它們?cè)谕粋€(gè)系統(tǒng)中能夠兼容,最好的實(shí)現(xiàn)方法是增加一個(gè)類似電源適配器一樣的適配器角色,通過(guò)適配器來(lái)協(xié)調(diào)這兩個(gè)原本不兼容的結(jié)構(gòu)。如何在軟件開(kāi)發(fā)中設(shè)計(jì)和實(shí)現(xiàn)適配器是本章我們將要解決的核心問(wèn)題,下面就讓我們正式開(kāi)始學(xué)習(xí)這種用于解決不兼容結(jié)構(gòu)問(wèn)題的適配器模式。
?
9.2 適配器模式概述
?????? 與電源適配器相似,在適配器模式中引入了一個(gè)被稱為適配器(Adapter)的包裝類,而它所包裝的對(duì)象稱為適配者(Adaptee),即被適配的類。適配器的實(shí)現(xiàn)就是把客戶類的請(qǐng)求轉(zhuǎn)化為對(duì)適配者的相應(yīng)接口的調(diào)用。也就是說(shuō):當(dāng)客戶類調(diào)用適配器的方法時(shí),在適配器類的內(nèi)部將調(diào)用適配者類的方法,而這個(gè)過(guò)程對(duì)客戶類是透明的,客戶類并不直接訪問(wèn)適配者類。因此,適配器讓那些由于接口不兼容而不能交互的類可以一起工作。
?????? 適配器模式可以將一個(gè)類的接口和另一個(gè)類的接口匹配起來(lái),而無(wú)須修改原來(lái)的適配者接口和抽象目標(biāo)類接口。適配器模式定義如下:
| 適配器模式(Adapter Pattern):將一個(gè)接口轉(zhuǎn)換成客戶希望的另一個(gè)接口,使接口不兼容的那些類可以一起工作,其別名為包裝器(Wrapper)。適配器模式既可以作為類結(jié)構(gòu)型模式,也可以作為對(duì)象結(jié)構(gòu)型模式。 |
【注:在適配器模式定義中所提及的接口是指廣義的接口,它可以表示一個(gè)方法或者方法的集合。】
?????? 在適配器模式中,我們通過(guò)增加一個(gè)新的適配器類來(lái)解決接口不兼容的問(wèn)題,使得原本沒(méi)有任何關(guān)系的類可以協(xié)同工作。根據(jù)適配器類與適配者類的關(guān)系不同,適配器模式可分為對(duì)象適配器和類適配器兩種,在對(duì)象適配器模式中,適配器與適配者之間是關(guān)聯(lián)關(guān)系;在類適配器模式中,適配器與適配者之間是繼承(或?qū)崿F(xiàn))關(guān)系。在實(shí)際開(kāi)發(fā)中,對(duì)象適配器的使用頻率更高,對(duì)象適配器模式結(jié)構(gòu)如圖9-3所示:
圖?9-3?對(duì)象適配器模式結(jié)構(gòu)圖
?????? 在對(duì)象適配器模式結(jié)構(gòu)圖中包含如下幾個(gè)角色:
?????? ●?Target(目標(biāo)抽象類):目標(biāo)抽象類定義客戶所需接口,可以是一個(gè)抽象類或接口,也可以是具體類。
?????? ●?Adapter(適配器類):適配器可以調(diào)用另一個(gè)接口,作為一個(gè)轉(zhuǎn)換器,對(duì)Adaptee和Target進(jìn)行適配,適配器類是適配器模式的核心,在對(duì)象適配器中,它通過(guò)繼承Target并關(guān)聯(lián)一個(gè)Adaptee對(duì)象使二者產(chǎn)生聯(lián)系。
?????? ●?Adaptee(適配者類):適配者即被適配的角色,它定義了一個(gè)已經(jīng)存在的接口,這個(gè)接口需要適配,適配者類一般是一個(gè)具體類,包含了客戶希望使用的業(yè)務(wù)方法,在某些情況下可能沒(méi)有適配者類的源代碼。
?????? 根據(jù)對(duì)象適配器模式結(jié)構(gòu)圖,在對(duì)象適配器中,客戶端需要調(diào)用request()方法,而適配者類Adaptee沒(méi)有該方法,但是它所提供的specificRequest()方法卻是客戶端所需要的。為了使客戶端能夠使用適配者類,需要提供一個(gè)包裝類Adapter,即適配器類。這個(gè)包裝類包裝了一個(gè)適配者的實(shí)例,從而將客戶端與適配者銜接起來(lái),在適配器的request()方法中調(diào)用適配者的specificRequest()方法。因?yàn)檫m配器類與適配者類是關(guān)聯(lián)關(guān)系(也可稱之為委派關(guān)系),所以這種適配器模式稱為對(duì)象適配器模式。典型的對(duì)象適配器代碼如下所示:
[java]?view plaincopy?
|
9.3 完整解決方案
????? Sunny軟件公司開(kāi)發(fā)人員決定使用適配器模式來(lái)重用算法庫(kù)中的算法,其基本結(jié)構(gòu)如圖9-4所示:
圖9-4??算法庫(kù)重用結(jié)構(gòu)圖
?????? 在圖9-4中,ScoreOperation接口充當(dāng)抽象目標(biāo),QuickSort和BinarySearch類充當(dāng)適配者,OperationAdapter充當(dāng)適配器。完整代碼如下所示:
[java]?view plaincopy???????為了讓系統(tǒng)具備良好的靈活性和可擴(kuò)展性,我們引入了工具類XMLUtil和配置文件,其中,XMLUtil類的代碼如下所示:
[java]?view plaincopy???????配置文件config.xml中存儲(chǔ)了適配器類的類名,代碼如下所示:
[html]?view plaincopy???????編寫(xiě)如下客戶端測(cè)試代碼:
[java]?view plaincopy??????? 編譯并運(yùn)行程序,輸出結(jié)果如下:
| 成績(jī)排序結(jié)果: 50,69,76,84,88,90,91,96, 查找成績(jī)90: 找到成績(jī)90。 查找成績(jī)92: 沒(méi)有找到成績(jī)92。 |
?????? 在本實(shí)例中使用了對(duì)象適配器模式,同時(shí)引入了配置文件,將適配器類的類名存儲(chǔ)在配置文件中。如果需要使用其他排序算法類和查找算法類,可以增加一個(gè)新的適配器類,使用新的適配器來(lái)適配新的算法,原有代碼無(wú)須修改。通過(guò)引入配置文件和反射機(jī)制,可以在不修改客戶端代碼的情況下使用新的適配器,無(wú)須修改源代碼,符合“開(kāi)閉原則”。
9.4?類適配器
???????除了對(duì)象適配器模式之外,適配器模式還有一種形式,那就是類適配器模式,類適配器模式和對(duì)象適配器模式最大的區(qū)別在于適配器和適配者之間的關(guān)系不同,對(duì)象適配器模式中適配器和適配者之間是關(guān)聯(lián)關(guān)系,而類適配器模式中適配器和適配者是繼承關(guān)系,類適配器模式結(jié)構(gòu)如圖9-5所示:
圖?9-5?類適配器模式結(jié)構(gòu)圖
?????? 根據(jù)類適配器模式結(jié)構(gòu)圖,適配器類實(shí)現(xiàn)了抽象目標(biāo)類接口Target,并繼承了適配者類,在適配器類的request()方法中調(diào)用所繼承的適配者類的specificRequest()方法,實(shí)現(xiàn)了適配。
?????? 典型的類適配器代碼如下所示:
[java]?view plaincopy?????? 由于Java、C#等語(yǔ)言不支持多重類繼承,因此類適配器的使用受到很多限制,例如如果目標(biāo)抽象類Target不是接口,而是一個(gè)類,就無(wú)法使用類適配器;此外,如果適配者Adapter為最終(Final)類,也無(wú)法使用類適配器。在Java等面向?qū)ο缶幊陶Z(yǔ)言中,大部分情況下我們使用的是對(duì)象適配器,類適配器較少使用。
|
??
?
9.5?雙向適配器
???????在對(duì)象適配器的使用過(guò)程中,如果在適配器中同時(shí)包含對(duì)目標(biāo)類和適配者類的引用,適配者可以通過(guò)它調(diào)用目標(biāo)類中的方法,目標(biāo)類也可以通過(guò)它調(diào)用適配者類中的方法,那么該適配器就是一個(gè)雙向適配器,其結(jié)構(gòu)示意圖如圖9-6所示:
圖9-6?雙向適配器結(jié)構(gòu)示意圖
?????? 雙向適配器的實(shí)現(xiàn)較為復(fù)雜,其典型代碼如下所示:
[java]?view plaincopy?????? 在實(shí)際開(kāi)發(fā)中,我們很少使用雙向適配器。
9.6 缺省適配器??????
???????缺省適配器模式是適配器模式的一種變體,其應(yīng)用也較為廣泛。缺省適配器模式的定義如下:| 缺省適配器模式(Default Adapter Pattern):當(dāng)不需要實(shí)現(xiàn)一個(gè)接口所提供的所有方法時(shí),可先設(shè)計(jì)一個(gè)抽象類實(shí)現(xiàn)該接口,并為接口中每個(gè)方法提供一個(gè)默認(rèn)實(shí)現(xiàn)(空方法),那么該抽象類的子類可以選擇性地覆蓋父類的某些方法來(lái)實(shí)現(xiàn)需求,它適用于不想使用一個(gè)接口中的所有方法的情況,又稱為單接口適配器模式。 |
?????? 缺省適配器模式結(jié)構(gòu)如圖9-7所示:
?
圖9-7??缺省適配器模式結(jié)構(gòu)圖
?????? 在缺省適配器模式中,包含如下三個(gè)角色:
??????●?ServiceInterface(適配者接口):它是一個(gè)接口,通常在該接口中聲明了大量的方法。
??????●?AbstractServiceClass(缺省適配器類):它是缺省適配器模式的核心類,使用空方法的形式實(shí)現(xiàn)了在ServiceInterface接口中聲明的方法。通常將它定義為抽象類,因?yàn)閷?duì)它進(jìn)行實(shí)例化沒(méi)有任何意義。
??????●?ConcreteServiceClass(具體業(yè)務(wù)類):它是缺省適配器類的子類,在沒(méi)有引入適配器之前,它需要實(shí)現(xiàn)適配者接口,因此需要實(shí)現(xiàn)在適配者接口中定義的所有方法,而對(duì)于一些無(wú)須使用的方法也不得不提供空實(shí)現(xiàn)。在有了缺省適配器之后,可以直接繼承該適配器類,根據(jù)需要有選擇性地覆蓋在適配器類中定義的方法。
?????? 在JDK類庫(kù)的事件處理包java.awt.event中廣泛使用了缺省適配器模式,如WindowAdapter、KeyAdapter、MouseAdapter等。下面我們以處理窗口事件為例來(lái)進(jìn)行說(shuō)明:在Java語(yǔ)言中,一般我們可以使用兩種方式來(lái)實(shí)現(xiàn)窗口事件處理類,一種是通過(guò)實(shí)現(xiàn)WindowListener接口,另一種是通過(guò)繼承WindowAdapter適配器類。如果是使用第一種方式,直接實(shí)現(xiàn)WindowListener接口,事件處理類需要實(shí)現(xiàn)在該接口中定義的七個(gè)方法,而對(duì)于大部分需求可能只需要實(shí)現(xiàn)一兩個(gè)方法,其他方法都無(wú)須實(shí)現(xiàn),但由于語(yǔ)言特性我們不得不為其他方法也提供一個(gè)簡(jiǎn)單的實(shí)現(xiàn)(通常是空實(shí)現(xiàn)),這給使用帶來(lái)了麻煩。而使用缺省適配器模式就可以很好地解決這一問(wèn)題,在JDK中提供了一個(gè)適配器類WindowAdapter來(lái)實(shí)現(xiàn)WindowListener接口,該適配器類為接口中的每一個(gè)方法都提供了一個(gè)空實(shí)現(xiàn),此時(shí)事件處理類可以繼承WindowAdapter類,而無(wú)須再為接口中的每個(gè)方法都提供實(shí)現(xiàn)。如圖9-8所示:
?
圖9-8? WindowListener和WindowAdapter結(jié)構(gòu)圖
?
9.7 適配器模式總結(jié)
????? 適配器模式將現(xiàn)有接口轉(zhuǎn)化為客戶類所期望的接口,實(shí)現(xiàn)了對(duì)現(xiàn)有類的復(fù)用,它是一種使用頻率非常高的設(shè)計(jì)模式,在軟件開(kāi)發(fā)中得以廣泛應(yīng)用,在Spring等開(kāi)源框架、驅(qū)動(dòng)程序設(shè)計(jì)(如JDBC中的數(shù)據(jù)庫(kù)驅(qū)動(dòng)程序)中也使用了適配器模式。
?
?????? 1.?主要優(yōu)點(diǎn)
?????? 無(wú)論是對(duì)象適配器模式還是類適配器模式都具有如下優(yōu)點(diǎn):
?????? (1)?將目標(biāo)類和適配者類解耦,通過(guò)引入一個(gè)適配器類來(lái)重用現(xiàn)有的適配者類,無(wú)須修改原有結(jié)構(gòu)。
?????? (2)?增加了類的透明性和復(fù)用性,將具體的業(yè)務(wù)實(shí)現(xiàn)過(guò)程封裝在適配者類中,對(duì)于客戶端類而言是透明的,而且提高了適配者的復(fù)用性,同一個(gè)適配者類可以在多個(gè)不同的系統(tǒng)中復(fù)用。
?????? (3)?靈活性和擴(kuò)展性都非常好,通過(guò)使用配置文件,可以很方便地更換適配器,也可以在不修改原有代碼的基礎(chǔ)上增加新的適配器類,完全符合“開(kāi)閉原則”。
????? 具體來(lái)說(shuō),類適配器模式還有如下優(yōu)點(diǎn):
????? 由于適配器類是適配者類的子類,因此可以在適配器類中置換一些適配者的方法,使得適配器的靈活性更強(qiáng)。
????? 對(duì)象適配器模式還有如下優(yōu)點(diǎn):
????? (1)?一個(gè)對(duì)象適配器可以把多個(gè)不同的適配者適配到同一個(gè)目標(biāo);
????? (2)?可以適配一個(gè)適配者的子類,由于適配器和適配者之間是關(guān)聯(lián)關(guān)系,根據(jù)“里氏代換原則”,適配者的子類也可通過(guò)該適配器進(jìn)行適配。
?
????? 2.?主要缺點(diǎn)
?????類適配器模式的缺點(diǎn)如下:
????? (1)?對(duì)于Java、C#等不支持多重類繼承的語(yǔ)言,一次最多只能適配一個(gè)適配者類,不能同時(shí)適配多個(gè)適配者;
????? (2)?適配者類不能為最終類,如在Java中不能為final類,C#中不能為sealed類;
????? (3)?在Java、C#等語(yǔ)言中,類適配器模式中的目標(biāo)抽象類只能為接口,不能為類,其使用有一定的局限性。
??????對(duì)象適配器模式的缺點(diǎn)如下:
????? 與類適配器模式相比,要在適配器中置換適配者類的某些方法比較麻煩。如果一定要置換掉適配者類的一個(gè)或多個(gè)方法,可以先做一個(gè)適配者類的子類,將適配者類的方法置換掉,然后再把適配者類的子類當(dāng)做真正的適配者進(jìn)行適配,實(shí)現(xiàn)過(guò)程較為復(fù)雜。
?
????? 3.?適用場(chǎng)景
????? 在以下情況下可以考慮使用適配器模式:
?????? (1)?系統(tǒng)需要使用一些現(xiàn)有的類,而這些類的接口(如方法名)不符合系統(tǒng)的需要,甚至沒(méi)有這些類的源代碼。
?????? (2)?想創(chuàng)建一個(gè)可以重復(fù)使用的類,用于與一些彼此之間沒(méi)有太大關(guān)聯(lián)的一些類,包括一些可能在將來(lái)引進(jìn)的類一起工作。
?
|
超強(qiáng)干貨來(lái)襲 云風(fēng)專訪:近40年碼齡,通宵達(dá)旦的技術(shù)人生
總結(jié)
以上是生活随笔為你收集整理的不兼容结构的协调——适配器模式的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 处理多维度变化——桥接模式
- 下一篇: 处理对象的多种状态及其相互转换——状态模