[Design Pattern] Adapter Pattern

重複使用一個介面與你期待不同之既存類別!!

官方說明

將一個類別的介面,轉換成另一個介面供客戶使用。轉接器讓原本介面不相容的類別可以合作無間。

 

主要療效

重複使用一個介面與你期待不同之既存類別

透過轉接器(Adapter)將不同介面類別(Adaptee)轉換至用戶(Client)期望的目標介面(ITarget)

image

 

適用情況

1. 轉換介面需求

2. 介面功能相似(有相對應功能)

 

關鍵心法

1. 建立轉接器(Adapter) [繼承期望的目標介面(ITarget)]

2. 透過物件合成方式包裝(wrap)被轉接者(Adaptee)

3. 將被轉接者(Adaptee)之方法對應至目標介面(ITarget)需實作的方法中

4. 客戶不須修改程式,直接以Adapter轉換Adaptee,透過原本的Itarget介面來操作Adaptee物件。

 

應用聯想

目前有一支遙控器(RemoteController)只支援操控具機械式開關(ISwitchable)檯燈,隨著科技日新月異,配合廠商開發了一批具觸碰式開關(ITouchable)檯燈,當然老闆希望遙控器也能夠控制新式觸碰行檯燈;分析一下需求,遙控器已是上市產品所以不允許被更動,開發觸碰式檯燈的廠商也不可能依照我們無理的要求實作ISwitchable介面來符合我們的需求,所以我們只能想辦法轉換ITouchable至ISwitchable介面來符合遙控器。以下我們將實際演練如何透過Adapter模式的協助,轉換不同介面物件至我們期待的介面。

 

首先來檢查是否合乎適用情境:

1. 轉換介面需求

    轉換ITouchable至ISwitchable介面來符合遙控器操控目標之介面需求

2. 介面功能相似(有相對應功能)

    ITouchable與ISwitchable介面皆有開啟(On)及關閉(Off)功能

 

來看看原本就存在的系統類別圖

image

 

* 機械式檯燈 (實作機械式開關介面)


interface ISwitchable
{
    // 機械式開啟
    void SwitchOn();

    // 機械式關閉
    void SwitchOff();
}

class SwitchableLamp : ISwitchable
{
    // 機械式開啟
    public void SwitchOn()
    {
        Console.WriteLine("開燈(使用機械式開關)");
    }

    // 機械式關閉
    public void SwitchOff()
    {
        Console.WriteLine("關燈(使用機械式開關)");
    }
}

 

* 遙控器


class RemoteController
{
    // 只支援操控"具有機械式開關特性"物件
    ISwitchable _switchableLamp;

    // 注入"具有機械式開關特性"物件實體
    public RemoteController(ISwitchable switchableLamp)
    {
        _switchableLamp = switchableLamp;
    }

    // 開啟
    public void TurnOn()
    {
        if (_switchableLamp != null)
        { _switchableLamp.SwitchOn(); }
    }

    // 關閉
    public void TurnOff()
    {
        if (_switchableLamp != null)
        { _switchableLamp.SwitchOff(); }
    }
}

*使用者操作方式


static void Main(string[] args)
{
    // 遙控器
    RemoteController remoteController;

    Console.WriteLine("使用遙控器控制 機械式式檯燈:");

    // 建立機械式開關檯燈
    ISwitchable switchableLight = new SwitchableLamp();

    // 注入遙控器欲操控之檯燈實體 (符合ISwitchable介面)
    remoteController = new RemoteController(switchableLight);

    // 開啟
    remoteController.TurnOn();

    // 關閉
    remoteController.TurnOff();


    Console.ReadKey();
}

輸出結果

image

 

接著新式觸碰式檯燈來了。


interface ITouchable
{
	// 觸碰式開啟
    void TouchOn();

    // 觸碰式關閉
    void TouchOff();
}

class TouchableLamp : ITouchable
{
    // 觸碰式開啟
    public void TouchOn()
    {
        Console.WriteLine("開燈(使用觸碰式開關)");
    }

    // 觸碰式關閉
    public void TouchOff()
    {
        Console.WriteLine("關燈(使用觸碰式開關)");
    }
}

首先面對的問題是,RemoteController無法注入非實作ISwitchable介面的檯燈,所以我們是無法達到老闆的需求利用同一支遙控器來控制新型觸碰式檯燈。所以筆者將搭配關鍵心法來實現介面轉換器(Adapter),讓新型觸碰式檯燈也列入遙控器支援範圍。

image

 

1. 建立轉接器(SwitchableLampAdapter) [繼承期望的目標介面(ISwitchable)]


class SwitchableLampAdapter : ISwitchable
{
	// 機械式開啟
    public void SwitchOn()
    {
        throw new NotImplementedException();
    }

    // 機械式關閉
    public void SwitchOff()
    {
        throw new NotImplementedException();
    }
}

 

2. 透過物件合成方式包裹(wrap)被轉接者(ITouchable/TouchableLamp)


class SwitchableLampAdapter : ISwitchable
{
    // 支援轉換"具有觸碰式開關特性"物件
    ITouchable _touchableLamp;
    
    // 注入"具有觸碰式開關特性"物件實體
    public SwitchableLampAdapter(ITouchable touchableLamp)
    {
        _touchableLamp = touchableLamp;
    }

    // 機械式開啟
    public void SwitchOn()
    {
        throw new NotImplementedException();
    }

    // 機械式關閉
    public void SwitchOff()
    {
        throw new NotImplementedException();
    }
}

 

3. 將被轉接者(ITouchable/TouchableLamp)之方法對應至目標介面(ISwitchable)需實作的方法中


class SwitchableLampAdapter : ISwitchable
{
    // 支援轉換"具有觸碰式開關特性"物件
    ITouchable _touchableLamp;

    // 注入"具有觸碰式開關特性"物件實體
    public SwitchableLampAdapter(ITouchable touchableLamp)
    {
        _touchableLamp = touchableLamp;
    }

    // 機械式開啟
    public void SwitchOn()
    {
        // 觸碰式開啟
        _touchableLamp.TouchOn();
    }

    // 機械式關閉
    public void SwitchOff()
    {
        // 觸碰式關閉
        _touchableLamp.TouchOff();
    }
}

4. 客戶不須修改程式,直接以Adapter轉換Adaptee,透過原本的Itarget介面來操作Adaptee物件。


static void Main(string[] args)
{
    // 遙控器
    RemoteController remoteController;
    
    Console.WriteLine("使用同一支遙控器控制觸碰式檯燈:");

    // 建立觸碰式開關檯燈
    ITouchable touchableLight = new TouchableLamp();

    // 透過轉接器將觸碰式開關檯燈,轉換為"假"的機械式開關檯燈
    ISwitchable fackSwitchableLight = new SwitchableLampAdapter(touchableLight);

    // 注入遙控器欲操控之檯燈實體 (符合ISwitchable介面)
    remoteController = new RemoteController(fackSwitchableLight);
    
    // 開啟
    remoteController.TurnOn();

    // 關閉
    remoteController.TurnOff();


    Console.ReadKey();
}

輸出結果

image

 

其他應用

在Teddy前輩的文章有提到,可以利用Adapter Pattern的特性來做為團隊開發的邊界連接器。試想個情境,A團隊需要B團隊開發的API_B來處理某種資料,此時B團隊尚未將API_B開發完畢,但專案總是要繼續走下去的,所以A團隊就可以自行搞個API_A來延續開發活動。等待B團隊API_B開發完畢後再透過Adapter Pattern來轉介API_A至API_B,此例亦是Pluggable Adapter的一種應用。

 

參考資料

http://teddy-chen-tw.blogspot.tw/2013/05/clean-codeadapter.html


希望此篇文章可以幫助到需要的人

若內容有誤或有其他建議請不吝留言給筆者喔 !