设计模式(七)—— 适配器模式

模式简介


将一个类的接口转换成客户希望的另外一个接口。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

posted @ 2018-05-28 20:15 Answer.Geng 阅读(...) 评论(...) 编辑 收藏