#### **參考文章**
[《Android源碼設計模式解析與實戰》讀書筆記(二十)](http://blog.csdn.net/qq_17766199/article/details/50514877)
[【設計模式】適配器模式](http://www.cnblogs.com/chenpi/p/5188384.html)
[Java之美[從菜鳥到高手演變]之設計模式二](http://blog.csdn.net/zhangerqing/article/details/8239539)
[ Android開發設計模式之——適配者模式 ](http://blog.csdn.net/beyond0525/article/details/22814129)
[《JAVA與模式》之適配器模式](http://www.cnblogs.com/java-my-life/archive/2012/04/13/2442795.html#3870969)
### **適配器模式**
適配器模式把一個類的接口變換成客戶端所期待的另一種接口,從而使原本因接口不匹配而無法在一起工作的兩個類能夠在一起工作。
說到底,適配器是將兩個不兼容的類融合在一起,它有點像粘合劑,將不同的東西通過一種轉換使得它們能夠協作起來,例如:經常碰到要在兩個沒有關系的類型之間進行交互,第一個解決方案就是修改各自類的接口,但是如果沒有源代碼或者我們不愿意為了一個應用而修改各自的接口,此時怎么辦?這種情況我們往往會使用一個Adapter,在這兩種接口之間創建一個“混血兒”接口,這個Adapter會將這兩個接口進行兼容,在不修改原有代碼的情況下滿足需求。
#### **適配器模式的用途**
用電器做例子,筆記本電腦的插頭一般都是三相的,即除了陽極、陰極外,還有一個地極。而有些地方的電源插座卻只有兩極,沒有地極。電源插座與筆記本電腦的電源插頭不匹配使得筆記本電腦無法使用。這時候一個三相到兩相的轉換器(適配器)就能解決此問題,而這正像是本模式所做的事情。
又或者如果你在歐洲某一個國家使用美國制造的筆記本電腦,你可能需要一個交流電適配器

#### **面向對象適配器**
假設有一個軟件系統,希望它能和一個新的廠商類庫搭配使用,但是這個新廠商所設計出來的接口,不同于舊廠商的接口
如下圖所示:

這個適配器就像是一個中間人,它將客戶所發出的請求轉換成廠商類能理解的請求

#### **適配器模式(對象適配器)解析**

**客戶使用適配器的過程如下**:
1. 客戶通過目標借口調用適配器的方法對適配器發出請求
2. 適配器使用被適配者接口把請求轉換成被設配者的一個或多個調用接口
3. 客戶接收到調用的結果,但并未察覺這一切是適配器在起轉換作用
>[info] **注意**:
>1. 客戶和被適配者是解耦的,一個不知道另一個。
>2. 如果系統中新舊并存,舊的部分希望舊的廠商接口,但是我們已經使用新廠商的接口編寫了這一部分,這時候,我們可以創建一個雙向的適配器,支持兩邊的接口。想創建一個雙向的接口,就必須實現所涉及到的兩個接口,這樣,這個適配器可以當作舊的接口,或者當做新的接口使用。
>3. 適配器模式將一個類的接口,轉換成客戶期望的另一個接口,適配器讓原來接口不兼容的類可以合作無間
>4. 適配器模式充滿良好的OO設計原則:使用對象組合,以修改的接口包裝被適配者。被適配者的任何子類,都可以搭配著適配器使用。
#### **使用場景**
1. 系統需要使用現有的類,但此類的接口不符合系統的需要,即接口不兼容。
2. 想要建立一個可以重復使用的類,用于與一些彼此之間沒有太大關聯的一些類,包括一些可能在將來引進的類一起工作。
3. 需要一個統一的輸出接口,而輸入端的類型不可預知。
#### **適配器模式的結構**
適配器模式有**類的適配器模式**和**對象的適配器模式**兩種不同的形式。

**對象的適配器**如下圖所示:

**類的適配器模式**如下圖所示:

#### **類適配器模式**:
類的適配器模式把適配的類的API轉換成為目標類的API。

如上圖所示:類適配器是**通過實現Target 接口以及繼承Adaptee 類來實現接口轉換**,例如,目標接口需要的是operation2 ,但是Adaptee 對象只有一個operation3 ,因此就出現了不兼容的情況。此時**通過Adapter 實現一個operation2 函數將Adaptee 的operation3 轉換為Target 需要的operation2,以此實現兼容。**
**角色介紹**:
* Target:目標角色,也就是所期待得到的接口。注意: 由于這里討論的是類適配器模式,因此目標不可以是類。
* Adaptee:現在需要適配的接口。
* Adapter:適配器角色,也是本模式的核心。適配器把源接口轉換成目標接口。顯然,這一角色不可以是接口,而必須是具體類。
#### **對象適配器模式**:
與類的適配器模式一樣,對象的適配器模式把被適配的類的API轉換成為目標類的API,與類的適配器模式不同的是,對象的適配器模式不是使用繼承關系連接到Adaptee ,而是使用代理關系連接到Adaptee類, UML類圖如圖所示。

**示例**:
以筆記本電源適配器為例,電源適配器將220V的電壓轉換到5V。那么5V電壓就是Target接口,220V電壓就是Adaptee類,轉換就是Adapter。
* **類適配器模式**:
**Target角色**
~~~
public interface FiveVolt {
public int getVolt5();
}
~~~
**Adaptee角色,需要被轉換的對象**
~~~
public class Volt220 {
public int getVolt220(){
return 220;
}
}
~~~
**Adapter角色,將220V的電壓轉換成5V的電壓**
~~~
public class VoltAdapter extends Volt220 implements FiveVolt{
@Override
public int getVolt5() {
return 5;
}
}
~~~
Target角色給出了需要的目標接口,而Adaptee類則是需要被轉換的對象。Adapter則是將Volt220轉換成Target的接口。對應的Target的目標是要獲取5V的輸出電壓,而Adaptee正常輸出電壓是220V,此時就需要電源適配器類將220V 的電壓轉換為5V 電壓,解決接口不兼容的問題。
~~~
public class Test {
public static void main(String[] args) {
VoltAdapter adapter = new VoltAdapter();
System.out.println("輸出電壓:" + adapter.getVolt5());
}
}
~~~
* **對象適配器模式**
對象適配器模式與類適配器模式不同的是,對象適配器模式不是使用繼承關系連接到Adapter類,而是**使用代理關系連接到Adapter類。**
所以FiveVolt 、Volt220 不變,VoltAdapter 修改如下:
~~~
public class VoltAdapter1 implements FiveVolt{
Volt220 mVolt220;
public VoltAdapter1(Volt220 adaptee) {
this.mVolt220 = adaptee;
}
public int getVolt220(){
return mVolt220.getVolt220();
}
@Override
public int getVolt5() {
return 5;
}
}
~~~
注意,這里為了節省代碼,我們并沒有遵循一些面向對象的基本原則。使用示例如下:
~~~
public class Test {
public static void main(String[] args) {
VoltAdapter adapter = new VoltAdapter(new Volt220());
System.out.println("輸出電壓:" + adapter.getVolt5());
}
}
~~~
這種實現方式直接將要適配的對象傳遞到Adapter中,使用組合的形式實現接口兼容的效果。這比類適配器方式更為靈活,同時被適配對象的方法不會暴露出來。而類適配器由于繼承了被適配對象,因此,被適配對象類的函數在Adapter 類中也都含有,這使得Adapter 類出現一些奇怪的接口,用戶使用成本較高。因此對象適配器模式更加靈活、實用。
在**實際開發中Adapter 通常應用于進行不兼容的類型轉換的場景,還有一種就是輸入有無數種情況,但是輸出類型是統一的,我們可以通過Adapter 返回一個統一的輸出,而具體的輸入留給用戶處理,內部只需知道輸出的是符合要求的類型即可**。例如ListView 的Adapter,用戶的Item View各式各樣,但最終都是屬于View 類型, ListView 只需要知道getView 返回的是一個View 即可,具體是什么View 類型并不需要ListView 關心。而**在使用Adapter 模式的過程中建議盡量使用對象適配器的實現方式,多用合成或者聚合,少用繼承。當然, 具體問題具體分析,根據需要來選用實現方式,最適合的才是最好的**。
#### **接口適配器模式**
除了前面2種適配器模式還有**接口適配器模式**。也叫做[**缺省適配模式**](http://baike.sogou.com/v71483729.htm?fromTitle=缺省適配器模式)
接口的適配器是這樣的:有時我們寫的一個接口中有多個抽象方法,當我們寫該接口的實現類時,必須實現該接口的所有方法,這明顯有時比較浪費,**因為并不是所有的方法都是我們需要的,有時只需要某一些**,此處為了解決這個問題,我們引入了接口的適配器模式,**借助于一個抽象類,該抽象類實現了該接口,實現了所有的方法,而我們不和原始的接口打交道,只和該抽象類取得聯系**,所以我們**寫一個類,繼承該抽象類,重寫我們需要的方法就行**。

有時可以這樣理解:適配新的業務需求的時候借助抽象實現類(AbsPhone實現Usb接口),也就說,抽象實現類把Usb接口中的行為都實現了,新的適配只需要跟抽象類對話就行,因為抽象實現類就能滿足了所有適配的需求,并且做到了只適配業務本身的行為,接口中不需要的行為我根本不需要關注。這就是抽象實現類的作用。類圖關系如下:

**示例如下**:
**抽象類AbsPhone實現**
~~~
/**
* 接口的適配器模式
* 1.借助于一個抽象類,該抽象類實現了該接口,實現了所有的方法
* 2.繼承類可以選擇性的實現接口中的方法
*
* @author xuzhaohu
*
*/
public abstract class AbsPhone implements Usb {
public void store() {
System.out.println("AbsPhone implements usb's store methond");
}
public void takeAlong() {
System.out.println("AbsPhone implements usb's takeAlong methond");
}
}
~~~
適配類只跟AbsPhone打交道,根本不需要關心接口的行為,只顯示自己所要關注的。
如Phone1適配只需要store()行為
~~~
public class Phone1 extends AbsPhone {
public void call() {
System.out.println("Phone1 call");
}
public void sms() {
System.out.println("Phone1 sms");
}
public void store() {
System.out.println("Phone1 need usb's store methond");
}
}
~~~
Phone2適配只需要takeAlong()行為
~~~
public class Phone2 extends AbsPhone {
public void call() {
System.out.println("Phone2 call");
}
public void sms() {
System.out.println("Phone2 sms");
}
public void takeAlong() {
System.out.println("Phone2 need usb's takeAlong methond");
}
}
~~~
實例化調用
~~~
Phone1 p1 = new Phone1();
Phone2 p2 = new Phone2();
p1.store();
p1.takeAlong();
p2.takeAlong();
p2.store();
~~~
輸出結果:
~~~
Phone1 need usb's store methond
AbsPhone implements usb's takeAlong methond
Phone2 need usb's takeAlong methond
AbsPhone implements usb's store methond
~~~
這樣很清晰的知道適配的什么方法了。
#### **缺省適配模式**
缺省適配(Default Adapter)模式為一個接口提供缺省實現,這樣子類型可以從這個缺省實現進行擴展,而不必從原有接口進行擴展。作為適配器模式的一個特例,缺省是適配模式在JAVA語言中有著特殊的應用。
**示例**:
**魯智深的故事**
和尚要做什么呢?吃齋、念經、打坐、撞鐘、習武等。如果設計一個和尚接口,給出所有的和尚都需要實現的方法,那么這個接口應當如下:
**接口:和尚**
~~~
public interface 和尚 {
public void 吃齋();
public void 念經();
public void 打坐();
public void 撞鐘();
public void 習武();
public String getName();
}
~~~
顯然,所有的和尚類都應當實現接口所定義的全部方法,不然就根本通不過JAVA語言編輯器。像下面的魯智深類就不行。
**類:魯智深**
~~~
public class 魯智深 implements 和尚{
public void 習武(){
拳打鎮關西;
大鬧五臺山;
大鬧桃花村;
火燒瓦官寺;
倒拔垂楊柳;
}
public String getName(){
return "魯智深";
}
}
~~~
由于魯智深只實現了getName()和習武()方法,而沒有實現任何其他的方法。因此,它根本就通不過Java語言編譯器。魯智深類只有實現和尚接口的所有的方法才可以通過Java語言編譯器,但是這樣一來魯智深就不再是魯智深了。以史為鑒,可以知天下。研究一下幾百年前魯智深是怎么剃度成和尚的,會對Java編程有很大的啟發。不錯,當初魯達剃度,眾僧說:“此人形容丑惡、相貌兇頑,不可剃度他",但是長老卻說:”此人上應天星、心地剛直。雖然時下兇頑,命中駁雜,久后卻得清凈。證果非凡,汝等皆不及他。”
原來如此!看來只要這里也應上一個天星的話,問題就解決了!使用面向對象的語言來說,“應”者,實現也;“天星”者,抽象類也。
**缺省適配類:天星**
~~~
public abstract class 天星 implements 和尚 {
public void 吃齋(){}
public void 念經(){}
public void 打坐(){}
public void 撞鐘(){}
public void 習武(){}
public String getName(){
return null;
}
}
~~~
魯智深類繼承抽象類“天星”
~~~
public class 魯智深 extends 天星{
public void 習武(){
拳打鎮關西;
大鬧五臺山;
大鬧桃花村;
火燒瓦官寺;
倒拔垂楊柳;
}
public String getName(){
return "魯智深";
}
}
~~~
這個抽象的天星類便是一個適配器類,魯智深實際上借助于適配器模式達到了剃度的目的。此適配器類實現了和尚接口所要求的所有方法。但是與通常的適配器模式不同的是,此適配器類給出的所有的方法的實現都是“平庸”的。這種“平庸化”的適配器模式稱作缺省適配模式。
在很多情況下,必須讓一個具體類實現某一個接口,但是這個類又用不到接口所規定的所有的方法。通常的處理方法是,這個具體類要實現所有的方法,那些有用的方法要有實現,那些沒有用的方法也要有空的、平庸的實現。
>[warning] **注意**:這些空的方法是一種浪費,有時也是一種混亂。除非看過這些空方法的代碼,程序員可能會以為這些方法不是空的。即便他知道其中有一些方法是空的,也不一定知道哪些方法是空的,哪些方法不是空的,除非看過這些方法的源代碼或是文檔。
缺省適配模式可以很好的處理這一情況。可以設計一個抽象的適配器類實現接口,此抽象類要給接口所要求的每一種方法都提供一個空的方法。就像幫助了魯智深的“上應天星”一樣,此抽象類可以使它的具體子類免于被迫實現空的方法。
#### **缺省適配模式的結構**
缺省適配模式是一種“平庸”化的適配器模式。

~~~
public interface AbstractService {
public void serviceOperation1();
public int serviceOperation2();
public String serviceOperation3();
}
~~~
~~~
public class ServiceAdapter implements AbstractService{
@Override
public void serviceOperation1() {
}
@Override
public int serviceOperation2() {
return 0;
}
@Override
public String serviceOperation3() {
return null;
}
}
~~~
可以看到,接口AbstractService要求定義三個方法,分別是serviceOperation1()、serviceOperation2()、serviceOperation3();而抽象適配器類ServiceAdapter則為這三種方法都提供了平庸的實現。因此,任何繼承自抽象類ServiceAdapter的具體類都可以選擇它所需要的方法實現,而不必理會其他的不需要的方法。
**適配器模式的用意**是要改變源的接口,以便于目標接口相容。**缺省適配的用意稍有不同**,它是為了方便建立一個不平庸的適配器類而提供的一種平庸實現。
在任何時候,如果不準備實現一個接口的所有方法時,就可以使用“缺省適配模式”制造一個抽象類,給出所有方法的平庸的具體實現。這樣,從這個抽象類再繼承下去的子類就不必實現所有的方法了。
#### **適配器模式的優點**
* **更好的復用性**
系統需要使用現有的類,而此類的接口不符合系統的需要。那么通過適配器模式就可以讓這些功能得到更好的復用。
* **更好的擴展性**
在實現適配器功能的時候,可以調用自己開發的功能,從而自然地擴展系統的功能。
#### **適配器模式的缺點**
過多的使用適配器,會讓系統非常零亂,不易整體進行把握。比如,明明看到調用的是A接口,其實內部被適配成了B接口的實現,一個系統如果太多出現這種情況,無異于一場災難。因此如果不是很有必要,可以不使用適配器,而是直接對系統進行重構。
#### **面向接口編程之適配器模式**
前言:
使用一個現成的類,但是它的接口不完全符合你的需求,我只想要它其中的一個方法,不想覆寫其他的方法。
比如,窗體有變大,變小,關閉的行為,但是我現在只需要關閉行為;
代碼如下
~~~
package reviewDemo;
//適配器模式:只想用其中的某一個方法,用適配器作為中間的過渡
interface Windows{
void max();
void min();
void close();
}
//適配器模式,實現接口所有的方法,但是不寫方法體!
class AdapterWindows implements Windows{
@Override
public void max() {
}
@Override
public void min() {
}
@Override
public void close() {
}
}
class MyWindows extends AdapterWindows{
//覆寫父類的方法
public void close(){
System.out.println("這個實現的是關閉功能!");
}
}
public class Demo17 {
public static void main(String[] args) {
new MyWindows().close();
}
}
~~~