### 一、問題引入
說起適配器其實在我們的生活中是非常常見的,比如:如果你到日本出差,你會發現日本的插座電壓都是110V的,而我們的手機充電器和筆記本充電器都是220V,所以你到了日本之后就沒辦法充電了,這時候我們通常會怎么辦呢,當然是使用一個升壓的變壓器將電壓升高到220V,這樣我們的手機通過一個變壓器(適配器)就能使用原本不能使用的插座了。
又比如說,有的國家的插座都是三孔的,而我們的手機大部分都是兩孔的,這是你也沒辦法直接把充電器插到插座上,這時我們可以使用一個適配器,適配器本身是三孔的,它可以直接插到三孔的插頭上,適配器本身可以提供一個兩孔的插座,然后我們的手機充電器就可以插到適配器上了,這樣我們原本只能插到兩孔上的插頭就能用三孔的插座了。
在我們的面向對象里也存在這個問題,假設一個軟件系統,你希望它能和一個新的廠商類庫搭配使用,但是這個新廠商所設計出來的接口,不同于舊廠商的接口,就像下圖這樣:

你不想改變現有的代碼,解決這個問題(而且你也不能改變廠商的代碼)。所以該怎么做?這個嘛,你可以寫一個類,將新廠商的接口轉化成你所希望的接口。

這個適配器工作起來就如同一個中間人,它將客戶所發出的請求轉換成廠商類能理解的請求。

這樣的話,就能在不改變現有代碼的情況下使用原本不匹配的類庫了。
### 二、適配器模式的相關概念
經過上邊的三個例子,我們可以總結出適配器模式的使用過程:
1、客戶通過目標接口調用適配器的方法對適配器發出請求。
2、適配器使用被適配者接口把請求轉化成被適配者的一個或多個調用接口。
3、客戶接收到調用的結果,但并未察覺這一切是適配在起轉化作用。
所以適配器模式的正式定義就是:
適配器模式將一個類的接口,轉化成客戶期望的另一個接口。適配器讓原本接口不兼容的類可以合作無間。
### 三、對象適配器
適配器其實是分為對象適配器和類適配器兩種,兩種的工作原理不太一樣。對象適配器是使用組合的方法,在Adapter中會保留一個原對象(Adaptee)的引用,適配器的實現就是講Target中的方法委派給Adaptee對象來做,用Adaptee中的方法實現Target中的方法。

這種類型的好處就是,Adpater只需要實現Target中的方法就好啦。
現在我們通過一個用火雞冒充鴨子的例子來看看如何使用適配器模式。
~~~
package com.designpattern.adapter.object;
public abstract class Duck {
/**
* 嘎嘎叫
*/
public abstract void quack();
public abstract void fly();
}
~~~
~~~
package com.designpattern.adapter.object;
public abstract class Turkey {
/**
* 火雞叫
*/
public abstract void gobble();
public abstract void fly();
}
~~~
~~~
package com.designpattern.adapter.object;
public class WildTurkey extends Turkey {
public void gobble() {
System.out.println("Gobble gobble");
}
public void fly() {
System.out.println("I'm flying a short distance");
}
}
~~~
~~~
package com.designpattern.adapter.object;
/**
* 用火雞冒充鴨子
* @author 98583
*
*/
public class TurkeyAdapter extends Duck {
/**
* 保留火雞的引用
*/
Turkey turkey;
public TurkeyAdapter(Turkey turkey) {
this.turkey = turkey;
}
/**
* 利用火雞的叫聲來實現鴨子的叫聲
*/
public void quack() {
turkey.gobble();
}
/**
* 利用火雞的飛的方法來實現鴨子的飛的方法
*/
public void fly() {
for (int i = 0; i < 5; i++) {
turkey.fly();
}
}
}
~~~
~~~
package com.designpattern.adapter.object;
/**
* 用火雞冒充鴨子
* @author 98583
*
*/
public class Client {
public static void main(String[] args) {
WildTurkey turkey = new WildTurkey();
Duck turkeyAdapter = new TurkeyAdapter(turkey);
System.out.println("The Turkey says...");
turkey.gobble();
turkey.fly();
System.out.println("\nThe TurkeyAdapter says...");
testDuck(turkeyAdapter);
}
static void testDuck(Duck duck) {
duck.quack();
duck.fly();
}
}
~~~
鴨子和火雞有相似之處,他們都會飛,雖然飛的不遠,他們不太一樣的地方就是叫聲不太一樣,現在我們有一個火雞的類,有鴨子的抽象類也就是接口。我們的適配器繼承自鴨子類并且保留了火雞的引用,重寫鴨子的飛和叫的方法,但是是委托給火雞的方法來實現的。在客戶端中,我們給適配器傳遞一個火雞的對象,就可以把它當做鴨子來使用了。
### 四、類適配器
與對象適配器不同的是,類適配器是通過類的繼承來實現的。Adpater直接繼承了Target和Adaptee中的所有方法,并進行改寫,從而實現了Target中的方法。

這種方式的缺點就是必須實現Target和Adaptee中的方法,由于Java不支持多繼承,所以通常將Target設計成接口,Adapter繼承自Adaptee然后實現Target接口。
我們使用類適配器的方式來實現一下上邊的用火雞來冒充鴨子。
~~~
package com.designpattern.adapter.classmethod;
/**
* 由于Java不支持多繼承,所以通常將Target聲明為接口
* @author 98583
*
*/
public interface Duck {
/**
* 嘎嘎叫
*/
public void quack();
public void duckFly();
}
~~~
~~~
package com.designpattern.adapter.classmethod;
/**
* 目前已有的火雞類的抽象類
* @author 98583
*
*/
public abstract class Turkey {
/**
* 火雞叫
*/
public abstract void gobble();
public abstract void turkeyFly();
}
~~~
~~~
package com.designpattern.adapter.classmethod;
/**
* 用火雞冒充鴨子,不再保留火雞類的引用,需要實現鴨子類和火雞類的方法
* @author 98583
*
*/
public class TurkeyAdapter extends Turkey implements Duck {
/**
* 利用火雞的叫聲來實現鴨子的叫聲
*/
public void quack() {
gobble();
}
/**
* 利用火雞的飛的方法來實現鴨子的飛的方法
*/
public void turkeyFly() {
for (int i = 0; i < 5; i++) {
System.out.println("I'm flying a short distance");
}
}
/**
* 使用火雞類的方法來實現鴨子類的方法
*/
public void duckFly() {
turkeyFly();
}
/**
* 火雞的叫聲
*/
public void gobble() {
System.out.println("Gobble gobble");
}
}
~~~
~~~
package com.designpattern.adapter.classmethod;
/**
* 用火雞冒充鴨子
* @author 98583
*
*/
public class Client {
public static void main(String[] args) {
Duck turkeyAdapter = new TurkeyAdapter();
System.out.println("\nThe TurkeyAdapter says...");
testDuck(turkeyAdapter);
}
static void testDuck(Duck duck) {
duck.quack();
duck.duckFly();
}
}
~~~
其實兩種方法的效果是一樣的,只是用的方法不一樣。Java不支持多繼承,所以將Duck聲明為接口,Adapter繼承自火雞類并且實現了Duck的方法,但是實現Duck的方法不再是委派給火雞類的對象,而是直接調用火雞類的方法,因為在Adapter中實現了火雞類的方法,所以可以直接調用。
### 五、缺省適配器
魯達剃度的故事就很好的說明了缺省適配器的作用。一般的和尚都是吃齋,念經,打坐,撞鐘和習武,但是魯達只是喝酒喝習武,所以‘魯達不能剃度(不能當做和尚使用),要想讓魯達可以當做和尚使用就要讓他實現和尚的所有方法,但是這樣做時候魯達就不是魯達了。我們可以找一個中間者,比如魯達是天星的一位,我們可以讓天星實現和尚所有的方法,再讓魯達繼承自天星。代碼如下:
這是定義的和尚接口,和尚都應該做以下的事。
~~~
package com.designpattern.adapter.defaultmethod;
public interface Monk {
public void chizha();
public void nianjing();
public void dazuo();
public void zhuangzhong();
public void xiwu();
}
~~~
這是天星類,為每個方法提供一個空實現,其他繼承自該類的子類可以重寫父類的方法。
~~~
package com.designpattern.adapter.defaultmethod;
public abstract class Star implements Monk{
public void chizha(){}
public void nianjing(){}
public void dazuo(){}
public void zhuangzhong(){}
public void xiwu(){}
}
~~~
魯達繼承自天星,并且添加了喝酒的方法。
~~~
package com.designpattern.adapter.defaultmethod;
public class Luda extends Star{
public void xiwu(){
System.out.println("魯達習武");
}
public void hejiu(){
System.out.println("魯達喝酒");
}
}
~~~
我們看到通過天星類(缺省適配器),魯達不需要再實現自己不需要的方法了。
### 六、優缺點
**優點:**
- 將目標類和適配者類解耦,通過引入一個適配器類來重用現有的適配者類,而無須修改原有代碼。
- 增加了類的透明性和復用性,將具體的實現封裝在適配者類中,對于客戶端類來說是透明的,而且提高了適配者的復用性。
- 靈活性和擴展性都非常好,通過使用配置文件,可以很方便地更換適配器,也可以在不修改原有代碼的基礎上增加新的適配器類,完全符合“開閉原則”。
**類適配器模式還具有如下優點:**
由于適配器類是適配者類的子類,因此可以在適配器類中置換一些適配者的方法,使得適配器的靈活性更強。
**對象適配器模式還具有如下優點:**
一個對象適配器可以把多個不同的適配者適配到同一個目標,也就是說,同一個適配器可以把適配者類和它的子類都適配到目標接口。
**缺點:**
**類適配器模式的缺點如下:**
對于Java、C#等不支持多重繼承的語言,一次最多只能適配一個適配者類,而且目標抽象類只能為抽象類,不能為具體類,其使用有一定的局限性,不能將一個適配者類和它的子類都適配到目標接口。
**對象適配器模式的缺點如下:**
與類適配器模式相比,要想置換適配者類的方法就不容易。如果一定要置換掉適配者類的一個或多個方法,就只好先做一個適配者類的子類,將適配者類的方法置換掉,然后再把適配者類的子類當做真正的適配者進行適配,實現過程較為復雜。
### 七、適用環境
1、系統需要使用現有的類,而這些類的接口不符合系統的需要。
2、想要建立一個可以重復使用的類,用于與一些彼此之間沒有太大關聯的一些類,包括一些可能在將來引進的類一起工作。
源碼下載:[http://download.csdn.net/detail/xingjiarong/9322221](http://download.csdn.net/detail/xingjiarong/9322221)
- 前言
- 設計原則(一)&quot;開-閉&quot;原則(OCP)
- 設計原則(二)里氏替換原則(LSP)
- 設計原則(三)組合復用原則
- 設計原則(四)依賴倒置原則(DIP)
- 設計模式(一)簡單工廠模式
- 設計模式(二)工廠方法模式
- 設計模式(三)抽象工廠模式
- 設計模式(四)單例模式
- 設計模式(五)創建者模式(Builder)
- 設計模式(六)原型模式
- 設計模式(七)門面模式(Facade Pattern 外觀模式)
- 設計模式(八)橋梁模式(Bridge)
- 設計模式(九)裝飾模式(Decorator)
- 設計模式(十)適配器模式
- 設計模式(十一)策略模式
- 設計模式(十二)責任鏈模式
- 設計模式之UML(一)類圖以及類間關系(泛化 、實現、依賴、關聯、聚合、組合)
- 設計模式之橋梁模式和策略模式的區別