1. 程式人生 > >設計模式(七)—— 適配器模式

設計模式(七)—— 適配器模式

[] 允許 swe 優點 ble sign cif pri ace

模式簡介


將一個類的接口轉換成客戶希望的另外一個接口。Adapter模式使得原本由於接口不兼容而不能一起工作的類可以一起工作。

Adpater模式又叫包裝器(Wrapper)。適配器模式既可以作為類結構型模式,也可以作為對象結構型模式。

結構說明


Adapter模式一般包含兩個版本:類適配器模式對象適配器模式。類適配器模式通過繼承一個類與接口(Java、C#等不支持多重繼承的語言),在接口的實現中調用適配者的方法,如下圖所示。

技術分享圖片

對象適配器通過對象的組合,適配器類中包含適配者的實例對象,在接口的實現方法中調用實例對象的方法。

技術分享圖片

角色說明

  • Adapter

適配器,對Adaptee與Target進行適配。

  • Adaptee

適配者,提供一個方法,但這個方法與Target接口不兼容,需要適配。

  • Target

特定領域相關使用的接口。

類適配器和對象適配器比較

  1. 類適配器的優點:因為Adapter是Adaptee的子類,所以可以在Adapter中重寫Adaptee的方法,使得Adapter更加靈活。
  2. 對象適配器的優點:一個對象適配器可以把Adaptee和它的子類全都適配到目標接口上。

示例分析


假設我們有一個轉換器程序,能夠將系統中的數據導出到不同格式的文件,只需要實現IExporter接口,客戶端就可以動態調用程序輸出不同格式的文件。

interface IExporter
{
    void Export();
}

class ExportToExcel : IExporter
{
    public void Export()
    {
        Console.WriteLine("Export to Excel...");
    }
}

//客戶端調用
static void Main(string[] args)
{
    IExporter exporter = new ExportToExcel();
    exporter.Export();
    Console.ReadLine();
}

公司購買了一個第三方類庫PDFWriter,使用這個類庫可以輕松地將數據導入到PDF文件中,但是,它只提供了WriteToPDF方法,這與我們的IExporter接口不兼容。

//這裏僅僅是為了方便展示示例提供,我們沒有第三方類庫的源碼
class PDFWriter
{
    public void WriteToPDF()
    {
        Console.WriteLine("Write to PDF..");
    }
}

由於沒有源碼,無法進行修改。當然,即使可以修改,也不應該為了實現一個應用去實現特定領域的接口。為了使我們的轉換器程序也能夠動態調用WriteToPDF方法,添加類PDFAdapter。

class PDFAdapter : IExporter
{
    private PDFWriter writer = new PDFWriter();
    public void Export()
    {
        writer.WriteToPDF();
    }
}

通過適配器進行動態調用。

static void Main(string[] args)
{
    IExporter exporter = new PDFAdapter();
    exporter.Export();
    Console.ReadLine();
}

雙向適配器(Two-way Adapter)


如果適配器同時包含目標類和適配者類的引用,適配者可以通過它調用目標類中的方法,目標類也可以通過它調用適配者類中的方法,那麽這個適配器就是一個雙向適配器,結構示意圖如下:

技術分享圖片

代碼如下:

interface IAdaptee
{
    void SpecificRequest();
}

class ConcreteAdaptee : IAdaptee
{
    public void SpecificRequest()
    {
        Console.WriteLine("SpecificRequest by ConcreteAdaptee");
    }
}

interface ITarget
{
    void Request();
}

class ConcreteTarget : ITarget
{
    public void Request()
    {
        Console.WriteLine("Request by ConcreteTarget");
    }
}

class Adapter : IAdaptee, ITarget
{
    private IAdaptee adaptee;
    private ITarget target;
    public Adapter(IAdaptee adaptee)
    {
        this.adaptee = adaptee;
    }
    public Adapter(ITarget target)
    {
        this.target = target;
    }
    public void Request()
    {
        adaptee.SpecificRequest();
    }

    public void SpecificRequest()
    {
        target.Request();
    }
}

可插入適配器(Pluggable Adapter)


觀察以上介紹的Adapter類,有一個共性,就是只能適配固定的Adaptee(編譯階段就已經確定),不夠靈活。而可插入適配器則是可以適配包含不同接口的Adaptee,換句話來說,可插入適配器允許適配在程序運行時動態傳入的適配者。下面我們介紹使用Action委托的方式來實現可插入適配器:

首先創建Cat類和Dog類,並且這兩個類擁有不同的方法。

class Cat
{
    public void Miaow()
    {
        Console.WriteLine("miao miao miao");
    }

}

class Dog
{
    public void Bark()
    {
        Console.WriteLine("wang wang wang");
    }

}

創建可插入適配器,由於不確定適配者的類型及接口,只知道將來客戶端要調用MakeSound方法。也就是說,適配器本身不知道將來會適配什麽樣的適配者,也不知道調用適配者的哪個方法,這一切都是由客戶端動態傳入。

class PluggableAdapter
{
    public Action MakeSound { get; private set; }
    public PluggableAdapter(Action makeSound)
    {
        this.MakeSound = makeSound;
    }
}

客戶端調用,向PluggableAdapter的構造函數傳入委托方法,完成適配。

static void Main(string[] args)
{
    Dog dog = new Dog();
    (new PluggableAdapter(dog.Bark)).MakeSound();
    Console.ReadLine();
}

適用場景


  1. 使用一個已經存在的類,但它提供的方法與系統中定義的接口不兼容

  2. 創建一個可以復用的類,用於與一些彼此之間沒有太大關聯的類,包括一些可能在將來引進的類一起工作
  3. 使用一些已經存在的子類,但是不可能對每一個都進行子類化以匹配它們的接口。對象適配器可以適配它們父類的接口

源碼下載


dotnet-design-pattern_adapter

設計模式(七)—— 適配器模式