聊聊Adapter模式

今天我们聊一个最简单的设计模式,适配器Adapter。跟以往一样,我们还是从一个例子出发。
 

一个例子

最开始的结构

假设我们有个数据分析软件,其中包含了数据收集器和数据分析器,数据收集器基于XML格式向数据分析器提供数据,有多种数据分析器,所以我们抽象出一个数据分析器的接口,用代码表示如下

    class XMLData { } //数据格式

    interface IDataConsumerXML //数据分析器接口
    {
        void Analyze(XMLData data);
    }

    class DataProviderXML
    {
        public XMLData data = new XMLData();
        public void ProvideDataTo(IDataConsumerXML consumer) //数据收集器,面向接口编程
        {
            Console.WriteLine("provide xml data");
            consumer.Analyze(data);
        }
    }

    class ConcreteAnalyzer1 : IDataConsumerXML 
    {
        //省略实现
    }

    class ConcreteAnalyzer2 : IDataConsumerXML 
    {
        //省略实现
    }

这一切运行完美。直到一天。。。

新的需求

突然我们发现有个数据分析器棒极了,我们非常想让它能和我们的数据收集器一起工作,可是很可惜,看起来它的代码是这样的,只能分析Json数据(现实世界中的例子中AnalyzerJson其实是从第三方库中来的,我们并没有它的源代码,仅仅有它的接口,即,它的代码不可变动)

    class JsonData {} //新的分析器需要的数据

    class AnalyzerJson
    {
        public void Analyze(JsonData data)
        {
            Console.WriteLine("Analyze JSON data");
        }
    }

我们发现这个分析器不具备和数据收集器一起工作的条件,它没有声明IDataConsumerXML也无法处理XML数据,而且我们也没有办法去修改它的代码,那我们应该怎么办呢?这个时候我们就需要适配器模式。
 

适配器模式

定义

适配器模式是一种结构型设计模式,它能使接口不兼容的对象能够相互合作

定义非常简洁,想要使不兼容对象合作,我们就需要一个类,

  • 这个类实现了不兼容的接口(IDataConsumerXML),
  • 这个类还负责把各种调用转交给具体的类(AnalyzerJson),同时完成必要的数据格式转换
    这个类就是适配器
     

具体实现

按照上面的说法,我们重构一下代码,添加一个适配器类

    class ConsumerXMLtoJSONAdapter : IDataConsumerXML //接口实现
    {
        private AnalyzerJson analyzer = null; 
        public ConsumerXMLtoJSONAdapter(AnalyzerJson analyzer)
        {
            this.analyzer = analyzer;
        }
        void IDataConsumerXML.Analyze(XMLData data) //调用转发
        {
            analyzer.Analyze(ConvertFromXMLData(data));
        }

        private JsonData ConvertFromXMLData(XMLData data) //必要的数据转换
        {
            Console.WriteLine("convert xml data to json data in adapter");
            //do some job for converting
            return new JsonData();
        }
    }

客户端使用也非常简单,确保通过适配器类去调用就可以了

        static void Main(string[] args)
        {
            DataProviderXML provider = new DataProviderXML();
            XMLData data = new XMLData();
            ConsumerXMLtoJSONAdapter adapter = new ConsumerXMLtoJSONAdapter(new AnalyzerJson());
            provider.ProvideDataTo(adapter);
        }

运行一下查看输出,一切正常,适配器适配成功。
接着我们看一下它的UML

(在一般的设计模式文献中,AnalyzerJson又叫做Adaptee,意为被适配者)
 

在C++中的变种

除了最常见的以合成的方式完成适配之外,在C++这种支持多继承的语言中还可以采用多继承,让适配器类继承接口和被适配者,这时UML如下

相应的C++代码如下

#include <iostream>

using namespace std;
class JsonData { };
class XMLData { };

class DataConsumerXML //c++里面没有接口
{
public:
    virtual void Analyze(XMLData data) const = 0;
};

class AnalyzerJson
{
public:
    void Analyze(JsonData data) const
    {
        cout << "Analyze JSON data" << endl;
    }
};

class DataProviderXML
{
public:
    void ProvideDataTo(const DataConsumerXML& consumer) const
    {
        cout << "provide xml data" << endl;
        consumer.Analyze(data);
    }
private:
    XMLData data;
};

class ConsumerXMLtoJSONAdapter : public DataConsumerXML, public AnalyzerJson
{
public:
    void Analyze(XMLData data) const override
    {
        JsonData d = ConvertFromXMLData(data);
        AnalyzerJson::Analyze(d);
    }

private:
    JsonData ConvertFromXMLData(XMLData) const
    {
        cout << "convert xml data to json data in adapter" << endl;
        //do some job for converting
        JsonData data;
        return data;
    }
};

int main()
{
    DataProviderXML provider;
    XMLData data;
    ConsumerXMLtoJSONAdapter adapter;
    provider.ProvideDataTo(adapter);
    return 0;
}

运行之后结果一样,可见在C++中,我们不但可以使用合成的方式实现适配器,还可以利用多重继承。这也说明了,虽然设计模式基本是语言中立的,但是不同语言在使用设计模式的时候也可以包含语言特色,切不可纸上谈兵哟。

总结

这就是适配器模式的介绍,一般来说当我们需要使用的类与接口不兼容但是我们又没有办法修改源代码(可能是因为第三方库没有源代码,也可能因为是老代码不能修改)的时候,会派上用场。
适配器算是设计模式中最简单的一个模式,合成 + 调用转发就构成了它,说到底,还是合成优于继承这个设计原则的又一次实践。可见设计模式绝非高不可攀,它只是前辈在实践中总结出来的、基于设计原则的、对于某种特定需求的最佳实践而已,希望大家在日常工作多多读代码,多多总结,一定要把握设计原则,这样学习设计模式就会事半功倍。