設計模式--介面卡模式Adapter(結構型)

benbenxiongyuan發表於2014-04-17

1. 概述:

         介面的改變,是一個需要程式設計師們必須(雖然很不情願)接受和處理的普遍問題。程式提供者們修改他們的程式碼;系統庫被修正;各種程式語言以及相關庫的發展和進化。

        例子1:iphone4,你即可以使用UBS介面連線電腦來充電,假如只有iphone沒有電腦,怎麼辦呢?蘋果提供了iphone電源介面卡。可以使用這個電源介面卡充電。這個iphone的電源介面卡就是類似我們說的介面卡模式。(電源介面卡就是把電源變成需要的電壓,也就是介面卡的作用是使得一個東西適合另外一個東西。)

       例子2:最典型的例子就是很多功能手機,每一種機型都自帶有從電器,有一天自帶充電器壞了,而且市場沒有這型別充電器可買了。怎麼辦?萬能充電器就可以解決。這個萬能充電器就是介面卡。

2. 問題

     你如何避免因外部庫的API改變而帶來的不便?假如你寫了一個庫,你能否提供一種方法允許你軟體的現有使用者進行完美地升級,即使你已經改變了你的API?為了更好地適宜於你的需要,你應該如何改變一個物件的介面?

3. 解決方案

        介面卡(Adapter)模式為物件提供了一種完全不同的介面。你可以運用介面卡(Adapter)來實現一個不同的類的常見介面,同時避免了因升級和拆解客戶程式碼所引起的糾紛。

    介面卡模式(Adapter Pattern),把一個類的介面變換成客戶端所期待的另一種介面, Adapter模式使原本因介面不匹配(或者不相容)而無法在一起工作的兩個類能夠在一起工作。又稱為轉換器模式、變壓器模式、包裝(Wrapper)器模式(把已有的一些類包裝起來,使之能有滿足需要的介面)。

     考慮一下當(不是假設!)一個第三方庫的API改變將會發生什麼。過去你只能是咬緊牙關修改所有的客戶程式碼,而情況往往還不那麼簡單。你可能正從事一項新的專案,它要用到新版本的庫所帶來的特性,但你已經擁有許多舊的應用程式,並且它們與以前舊版本的庫互動執行地很好。你將無法證明這些新特性的利用價值,如果這次升級意味著將要涉及到其它應用程式的客戶程式碼。

4. 分類

共有兩類介面卡模式:1.類的介面卡模式(採用繼承實現)2.物件介面卡(採用物件組合方式實現)

1)類介面卡模式    ——介面卡繼承自已實現的類(一般多重繼承)。

Adapter與Adaptee是繼承關係
1、用一個具體的Adapter類和Target進行匹配。結果是當我們想要一個匹配一個類以及所有它的子類時,類Adapter將不能勝任工作
2、使得Adapter可以重定義Adaptee的部分行為,因為Adapter是Adaptee的一個子集
3、僅僅引入一個物件,並不需要額外的指標以間接取得adaptee

2)物件介面卡模式—— 介面卡容納一個它包裹的類的例項。在這種情況下,介面卡呼叫被包裹物件的物理實體。

Adapter與Adaptee是委託關係
1、允許一個Adapter與多個Adaptee同時工作。Adapter也可以一次給所有的Adaptee新增功能
2、使用重定義Adaptee的行為比較困難
無論哪種介面卡,它的宗旨都是:保留現有類所提供的服務,向客戶提供介面,以滿足客戶的期望。
即在不改變原有系統的基礎上,提供新的介面服務。

5. 適用性

以下情況使用Adapter模式
1 • 你想使用一個已經存在的類,而它的介面不符合你的需求。
2 • 你想建立一個可以複用的類,該類可以與其他不相關的類或不可預見的類(即那些介面可能不一定相容的類)協同工作。
3 •(僅適用於物件Adapter)你想使用一些已經存在的子類,但是不可能對每一個都進行子類化以匹配它們的介面。物件介面卡可以適配它的父類介面。即僅僅引入一個物件,並不需要額外的指標以間接取得adaptee。

6. 結構

類介面卡使用多重繼承對一個介面與另一個介面進行匹配,如下圖所示:


物件匹配器依賴於物件組合,如下圖所示:


7. 構建模式的組成

目標角色(Target):— 定義Client使用的與特定領域相關的介面。
客戶角色(Client):與符合Target介面的物件協同。
被適配橘色(Adaptee):定義一個已經存在並已經使用的介面,這個介面需要適配。
介面卡角色(Adapte) :介面卡模式的核心。它將對被適配Adaptee角色已有的介面轉換為目標角色Target匹配的介面。對Adaptee的介面與Target介面進行適配.

8. 效果

類介面卡和物件介面卡有不同的權衡。
類介面卡
• 用一個具體的Adapter類對Adaptee和Target進行匹配。結果是當我們想要匹配一個類以及所有它的子類時,類Adapter將不能勝任工作。
• 使得Adapter可以重定義Adaptee的部分行為,因為Adapter是Adaptee的一個子類。
• 僅僅引入了一個物件,並不需要額外的指標以間接得到 Adaptee。

物件介面卡則
• 允許一個Adapter與多個Adaptee—即Adaptee本身以及它的所有子類(如果有子類的話)—同時工作。Adapter也可以一次給所有的Adaptee新增功能。
• 使得重定義Adaptee的行為比較困難。這就需要生成Adaptee的子類並且使得Adapter引用這個子類而不是引用Adaptee本身。

使用Adapter模式時需要考慮的其他一些因素有

1) Adapter的匹配程度 對Adaptee的介面與Target的介面進行匹配的工作量各個Adapter可能不一樣。工作範圍可能是,從簡單的介面轉換(例如改變操作名 )到支援完全不同的操作集合。Adapter的工作量取決於Target介面與Adaptee介面的相似程度
2) 可插入的Adapter   當其他的類使用一個類時,如果所需的假定條件越少,這個類就更具可複用性。如果將介面匹配構建為一個類,
就不需要假定對其他的類可見的是一個相同的介面。也就是說,介面匹配使得我們可以將自己的類加入到一些現有的系統中去,
而這些系統對這個類的介面可能會有所不同。 
3) 使用雙向介面卡提供透明操作 使用介面卡的一個潛在問題是,它們不對所有的客戶都透明。被適配的物件不再相容 Adaptee的介面,
因此並不是所有 Adaptee物件可以被使用的地方它都可以被使用。雙向介面卡提供了這樣的透明性。
在兩個不同的客戶需要用不同的方式檢視同一個物件時,雙向介面卡尤其有用。


9. 實現

類介面卡使用的是繼承

讓我們看看當API改變時,如何保護應用程式不受影響。

<?php
/**
 * 類介面卡模式
 * @author guisu
 * 
 */
 
/**
 * 目標角色
 * @version 1.0
 */
class Target {
 
    /**
     * 這個方法將來有可能改進
     */
    public function hello(){
    	echo 'Hello ';
    }
 
    /**
     * 目標點
     */
    public function world(){
    	echo 'world';
    }
}
 
/**
 * Client 程式
 *
 */
class Client {

    /**
     * Main program.
     */
    public static function main() {
        $Target = new Target();
        $Target->hello();
        $Target->world();
 
    }
 
}
Client::main();
?>

我們Target已經明確指出hello()方法會在未來的版本中改進,甚至不被支援或者淘汰。接下來,現在假設第二版的Target已經發布。一個全新的greet()方法代替了hello()。

<?php
/**
 * 類介面卡模式
 * @author guisu
 * 
 */
 
/**
 * 目標角色
 * @version 2.0
 */
class Target {
 
    /**
     * 這個方法將來有可能繼續改進
     */
    public function greet(){
    	echo 'Greet ';
    }
 
    /**
     * 目標點
     */
    public function world(){
    	echo 'world';
    }
}
如果我們繼續使用原來的client程式碼,肯定會報錯,找不到hello方法。

針對API“升級”的解決辦法就是建立一個介面卡(Adapter)。

類介面卡使用的是繼承

<?php
/**
 * 類介面卡模式
 * @author guisu
 * 
 */
 
/**
 * 目標角色
 * @version 2.0
 */
interface Target {
 
    /**
     * 源類的方法:這個方法將來有可能繼續改進
     */
    public function hello();
 
    /**
     * 目標點
     */
    public function world();
}
 
/**
 * 源角色:被適配的角色
 */
class Adaptee {
	/**
     * 源類含有的方法
     */
    public function world() {
        echo ' world <br />';
    }
 
    /**
     * 加入新的方法
     */
    public function greet() {
        echo ' Greet ';
    }
}
 
/**
 * 類介面卡角色
 */
class Adapter extends Adaptee implements Target {
 
    /**
     * 源類中沒有world方法,在此補充
     */
    public function hello() {
       parent::greet();
    }
 
}
/**
 * 客戶端程式
 *
 */
class Client {
 
    /**
     * Main program.
     */
    public static function main() {
        $adapter = new Adapter();
        $adapter->hello();
        $adapter->world();
    }
}
Client::main();
?>

物件介面卡使用的是委派

<?php
/**
 * 類介面卡模式
 * @author guisu
 * 
 */
 
/**
 * 目標角色
 * @version 2.0
 */
interface Target {
 
    /**
     * 源類的方法:這個方法將來有可能繼續改進
     */
    public function hello();
 
    /**
     * 目標點
     */
    public function world();
}
 
/**
 * 源角色:被適配的角色
 */
class Adaptee {
	/**
     * 源類含有的方法
     */
    public function world() {
        echo ' world <br />';
    }
 
    /**
     * 加入新的方法
     */
    public function greet() {
        echo ' Greet ';
    }
}
 
/**
 * 類介面卡角色
 */
class Adapter  implements Target {

	private $_adaptee;
 	/**
 	 * construct
 	 *
 	 * @param Adaptee $adaptee
 	 */
    public function __construct(Adaptee $adaptee) {
        $this->_adaptee = $adaptee;
    }
 
    /**
     * 源類中沒有world方法,在此補充
     */
    public function hello() {
       $this->_adaptee->greet();
    }
 
    /**
     * 源類中沒有world方法,在此補充
     */
    public function world() {
       $this->_adaptee->world();
    }
}
/**
 * 客戶端程式
 *
 */
class Client {
 
    /**
     * Main program.
     */
    public static function main() {
    	$adaptee = new Adaptee();
        $adapter = new Adapter($adaptee);
        $adapter->hello();
        $adapter->world();
    }
}

Client::main();
?>

如例中程式碼所示,你可以運用介面卡(Adapter)模式來避免因外部庫改變所帶來的不便——倘若向上相容。作為某個庫的開發者,你應該獨立編寫介面卡,使你的使用者更簡便地使用新版本的庫,而不用去修改他們現有的全部程式碼。

     GoF書中提出的介面卡(Adapter)模式更傾向於運用繼承而不是組成。這在強型別語言中是有利的,因為介面卡(Adapter)事實上是一個目標類的子類,因而能更好地與類中方法相結合。

了更好的靈活性,我個人比較傾向於組成的方法(特別是在結合了依賴性倒置的情況下);儘管如此,繼承的方法提供兩種版本的介面,或許在你的實際運用中反而是一個提高靈活性的關鍵。

10.介面卡模式與其它相關模式

橋樑模式(bridge模式)橋樑模式與物件介面卡類似,但是橋樑模式的出發點不同:橋樑模式目的是將介面部分和實現部分分離,從而對它們可以較為容易也相對獨立的加以改變。而物件介面卡模式則意味著改變一個已有物件的介面

裝飾器模式(decorator模式):裝飾模式增強了其他物件的功能而同時又不改變它的介面。因此裝飾模式對應用的透明性比介面卡更好。結果是decorator模式支援遞迴組合,而純粹使用介面卡是不可能實現這一點的。

Facade(外觀模式介面卡模式的重點是改變一個單獨類的API。Facade的目的是給由許多物件構成的整個子系統,提供更為簡潔的介面。而介面卡模式就是封裝一個單獨類,介面卡模式經常用在需要第三方API協同工作的場合,設法把你的程式碼與第三方庫隔離開來。

介面卡模式與外觀模式都是對現相存系統的封裝。但這兩種模式的意圖完全不同,前者使現存系統與正在設計的系統協同工作而後者則為現存系統提供一個更為方便的訪問介面。簡單地說,介面卡模式為事後設計,而外觀模式則必須事前設計,因為系統依靠於外觀。總之,介面卡模式沒有引入新的介面,而外觀模式則定義了一個全新的介面。


代理模式(Proxy )在不改變它的介面的條件下,為另一個物件定義了一個代理。

裝飾者模式,介面卡模式,外觀模式三者之間的區別:

裝飾者模式的話,它並不會改變介面,而是將一個一個的介面進行裝飾,也就是新增新的功能。

介面卡模式是將一個介面通過適配來間接轉換為另一個介面。

外觀模式的話,其主要是提供一個整潔的一致的介面給客戶端。

 

轉自:http://blog.csdn.net/hguisu/article/details/7527842

相關文章