重溫《深入淺出設計模式》轉接器模式 (Book Review of Head First Design Pattern, Adapter Pattern)

2020-07-03

轉接器模式常與表象模式進行比較,從使用上的觀點來看,前者是為了將類別能夠配合多型的方法使用,將目標類別以介面的方式,實作一個轉接器來轉換被既有的類別來達成;表象模式則是將龐雜的類別包裝成簡易的介面,讓使用者與龐雜的類別保持低耦合、最小知識,同時易於使用。

logo

轉接器模式

Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn't otherwise because of incompatible interfaces.

轉接器模式的 UML

After Pattern

轉接器模式可以透過合成的方式,藉由實作介面的達成物件的用途轉型,並且讓客戶端使用上不會感覺到物件類別的差異。Duck Adapter 實作的是 Turkey interface 所以是將 duck 轉為 turkey 多型使用;Turkey Adapter 則相反,是將 turkey 轉為 duck 多型使用。

轉接器是一個輕巧簡易使用的模式,在使用上只要利用轉接器去實作要多型使用對象的 Interface 即可完成,然而在實際的情境可能要多型使用的對象是類別而非 Interface,這時或可先將類別的要多型使用的方法抽出成 Interface,並且讓轉接器去實作此類別來完成。

DuckAdapter.cs
IDuck.cs
<- MallardDuck.cs

public interface IDuck
{
    void Fly();
    void Quack();
}

public class MallardDuck : IDuck
{
    public void Quack()
    {
        Console.WriteLine("Quack (Duck style)");
    }

    public void Fly()
    {
        Console.WriteLine("I'm flying (Duck style)");
    }
}

// From Duck Class To Turkey Class
public class DuckAdapter : ITurkey
{
    private readonly IDuck _duck;

    public DuckAdapter(IDuck duck)
    {
        this._duck = duck;
    }

    public void Gobble()
    {
        _duck.Quack();
    }

    public void Fly()
    {
        _duck.Fly();
    }
}

TurkeyAdapter.cs
ITurkey.cs
<- WildTurkey.cs

public interface ITurkey
{
    void Gobble();
    void Fly();
}

public class WildTurkey : ITurkey
{
    public void Gobble()
    {
        Console.WriteLine("Gobble gobble (Turkey style)");
    }

    public void Fly()
    {
        Console.WriteLine("I'm flying a short distance (Turkey style)");
    }
}

// From Turkey Class To Duck Class
public class TurkeyAdapter : IDuck
{
    private readonly ITurkey _turkey;

    public TurkeyAdapter(ITurkey turkey)
    {
        this._turkey = turkey;
    }

    void IDuck.Fly()
    {
        for (int i = 0; i < 5; i++)
        {
            _turkey.Fly();
        }
    }

    void IDuck.Quack()
    {
        _turkey.Gobble();
    }
}

DuckTestDrive.cs
TurkeyTestDrive.cs

public static void Main(string[] args)
{
    MallardDuck duck = new MallardDuck();
    ITurkey duckAdapter = new DuckAdapter(duck);

    Console.WriteLine("A Duck is doing Turkey things : ");
    duckAdapter.Gobble();
    duckAdapter.Fly();

    Console.WriteLine("\n-------------------------------------------------\n");

    WildTurkey turkey = new WildTurkey();
    IDuck turkeyAdapter = new TurkeyAdapter(turkey);
    Console.WriteLine("A Turkey is doing Duck things : ");
    turkeyAdapter.Quack();
    turkeyAdapter.Fly();
}

小結

✔️將變動的部分封裝起來
✔️類別應該對需求保持開放;應該對既有的程式碼修改保持關閉
被轉接的類別 (Adaptee) 可能會有變動,但透過 Adapter 使用的 Client 不會受影響

✔️多用合成,少用繼承
Adapter 使用合成的方式將 Adaptee 包裝在其中

✔️讓需要互動的物件之間的關係鬆綁
Client 與 Adaptee 低耦合

✔️針對介面撰寫,而非針對實踐方式撰寫
Target 是 Interface , Adapter 針對 Target 去撰寫演算邏輯。