1. 程式人生 > >設計模式——介面卡模式 Java原始碼

設計模式——介面卡模式 Java原始碼

前言

《Head First Design Patterns》給的程式碼的例子是關於鴨子和火雞,然而鴨子和火雞離日常生活比較遠。這次,我改編了實驗樓網站上面的例子,關於插座和充電器。

充電插頭
圖:不同國家的插座,插頭不一樣,呵呵噠

介面卡
圖:所以需要寫一個介面卡模式

介面卡類圖
圖:我繪製的介面卡類圖

情景:美國的插座,提供110伏電壓;中國的插座,提供220伏電壓。

  1. 在中國,用兩孔插座充電
  2. 然後坐飛機去美國旅遊,假設美國某旅館的牆上有隻有一個三孔插座
  3. 幸好我有美國介面卡,一頭插到三孔插座,另一頭轉換成二孔插座,就可以給我的榮耀手機充電
  4. 在美國,通過美國介面卡,用三空插座充電

總共7個類
一個三孔插座介面(Adaptee, 被適配者)
一個三孔插座類
一個兩孔插座介面(Target, 適配目標)
一個兩孔插座類
一個介面卡(Adapter:實現Target, 組合Adaptee


一個手機類(Client)
一個Main類,用於測試

talk is cheap, show me the code

三孔插座介面(Adaptee)

package adapter;

// adaptee(被適配者) ———— 假設在美國某旅館的牆上,只有一個三孔插座
public interface ThreePinSoket
{
    public void chargeWithThreePin();
    public int  voltage();
}

三孔插座類

package adapter;

// 實現一個具體的 adaptee
public
class ThreePinSoketAmerica implements ThreePinSoket { @Override public void chargeWithThreePin() { System.out.println("美國標準的三孔的插座"); } @Override public int voltage() { return 110; // 美國電壓是110伏 } }

兩孔插座介面(Target)

package adapter;

// target(適配目標) ———— 我的榮耀手機充電器是兩個插頭,所以需要兩個插孔的插座
public interface TwoPinSoket { public void chargeWithTwoPin(); public int voltage(); }

兩孔插座類

package adapter;

// client(具體的adaptee) ———— 這個就是我在中國的牆上的兩個插孔的插座,我充電只能用這個
public class TwoPinSoketChina implements TwoPinSoket
{

    @Override
    public void chargeWithTwoPin()
    {
        System.out.println("中國標準的兩孔的插座");
    }

    @Override
    public int voltage()
    {
        return 220;      // 中國電壓是220伏
    }

}

介面卡(Adapter)

實現Target, 組合Adaptee

package adapter;

// 去美國旅遊,必須帶上一個“美國介面卡”:實現兩孔插座,組合三孔插座。用來給我的榮耀手機充電
public class AmericaAdapter implements TwoPinSoket // 實現兩孔插座(target)
{
    ThreePinSoket threePinSoket; // 組合三孔插座(adaptee)

    public AmericaAdapter(ThreePinSoket threePinSoket)
    {
        this.threePinSoket = threePinSoket;
    }

    @Override
    public void chargeWithTwoPin()
    {
        threePinSoket.chargeWithThreePin();
    }

    @Override
    public int voltage()
    {
        return threePinSoket.voltage() * 2; // 介面卡把電壓從 110V 升到 220V 
    }

}

手機類(Client)

package adapter;

public class RongYao
{
    TwoPinSoket twoPinSoket;

    public RongYao() {}

    public void setTwoPinSoket(TwoPinSoket twoPinSoket)
    {
        this.twoPinSoket = twoPinSoket;
    }

    public void chargeRequest()
    {
        System.out.println("華為榮耀手機, " + twoPinSoket.voltage() + " 伏特充電中\n");
    }

}

Main類,用於測試

package adapter;

public class Main
{

    public static void main(String[] args)
    {
        // 在中國,用兩孔插座充電
        TwoPinSoketChina twoPinSoketChina = new TwoPinSoketChina();
        RongYao myRongYao = new RongYao();
        myRongYao.setTwoPinSoket(twoPinSoketChina);
        myRongYao.chargeRequest();

        // 然後坐飛機去美國旅遊,美國某旅館的牆上有隻有一個三孔插座
        ThreePinSoketAmerica threePinSoketAmerica = new ThreePinSoketAmerica();
        testThreePin(threePinSoketAmerica);

        // 幸好我有美國介面卡,一頭插到三孔插座,另一頭轉換成二孔插座,就可以給我的榮耀手機充電
        AmericaAdapter americaAdapter = new AmericaAdapter(threePinSoketAmerica);
        testTwoPin(americaAdapter);

        // 在美國,通過美國介面卡,用三空插座充電
        myRongYao.setTwoPinSoket(americaAdapter);
        myRongYao.chargeRequest();

    }

    static void testTwoPin(TwoPinSoket twoPinSoket)
    {
        twoPinSoket.chargeWithTwoPin();
        System.out.println("電壓是" + twoPinSoket.voltage() + "伏特\n");
    }

    static void testThreePin(ThreePinSoket threePinSoket)
    {
        threePinSoket.chargeWithThreePin();
        System.out.println("電壓是" + threePinSoket.voltage() + "伏特\n");
    }
}

執行結果

直接從eclipse複製過來

華為榮耀手機, 220 伏特充電中

美國標準的三孔的插座
電壓是110伏特

美國標準的三孔的插座
電壓是220伏特

華為榮耀手機, 220 伏特充電中

分析

介面卡模式有三個重要角色:

  • 目標角色(Target),要轉換成的目標介面。在我的程式碼例子中,是中國的兩孔介面
  • 源角色(Adaptee),需要被轉換的源介面。在我的程式碼例子中,是美國的三孔介面
  • 介面卡角色(Adapter),核心是實現Target介面, 組合Adaptee介面

這樣,Adaptee和Target兩個原本不相容的介面,就可以在一起工作了(我的榮耀手機就可以在美國充電了)。這裡的面向介面程式設計,得到了鬆耦合的效果。

美國的三孔插座可以實現Adaptee介面,那麼英國、法國的三孔插座也可以去實現Adaptee介面,它們都成為了Adaptee介面的子類。在Adapter類中,由於組合了一個Adaptee的引用,根據Java的多型性,我就可以拿著相同的Adapter類去英國,法國充電了。

另一方面,Client類組合一個Target介面的引用。我們就可製造多個Adapter類,實現同一個Target介面。假設索尼手機的需要日本標準的兩孔插座,那麼寫一個日本兩孔插座類實現Target介面,我就可以拿著相同的Adapter類,在美國給日本的索尼手機充電了。

最後補充一點:《Head First Design Patterns》說到,介面卡模式其實分為兩種。一種是Object Adapter,另外一種是Class Adapter。本篇部落格就是一個Object Adapter的例子。那麼Class Adapter是長什麼樣子的呢?它繼承Adaptee類,實現Target介面 。《設計模式 (Java版)》中的例子就是這樣的。