聊聊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++中,我們不但可以使用合成的方式實現適配器,還可以利用多重繼承。這也說明了,雖然設計模式基本是語言中立的,但是不同語言在使用設計模式的時候也可以包含語言特色,切不可紙上談兵喲。
總結
這就是適配器模式的介紹,一般來說當我們需要使用的類與接口不兼容但是我們又沒有辦法修改源代碼(可能是因為第三方庫沒有源代碼,也可能因為是老代碼不能修改)的時候,會派上用場。
適配器算是設計模式中最簡單的一個模式,合成 + 調用轉發就構成了它,說到底,還是合成優于繼承這個設計原則的又一次實踐。可見設計模式絕非高不可攀,它只是前輩在實踐中總結出來的、基于設計原則的、對于某種特定需求的最佳實踐而已,希望大家在日常工作多多讀代碼,多多總結,一定要把握設計原則,這樣學習設計模式就會事半功倍。