1. 简介
适配器模式(Adapter),将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。是属于类对象结构型模式的一种。
适配器模式(Adapter)主要应用于希望复用一些现存的类,但是接口又与复用环境要求不一致的情况。简单地说,就是需要的东西就在面前,但却不能使用,于是我们就想办法适配它。
这样说起来可能有些抽象,就以生活中的例子来形象的表述下。大部分使用过安卓手机的朋友对于来说,在很长的一段时间内都是使用micro USB类型的充电口的,直到后来USB Type-C的出现,使得现阶段的手机都采用这种接口。由于这两个接口是不匹配的,那么之前的micro USB线是无法插入USB Type-C类型的手机的,于是就出现了micro usb 转type c转接头(说白了这就是适配器了)。
在GoF的设计模式中,将适配器模式分为两种类型:
类适配器模式和
对象适配器模式。
2. 结构图
类适配器使用多重继承对一个接口与另一个接口进行匹配,如下图所示:
对象适配器依赖于对象组合,如下图所示:
- Target:定义Client使用的与特定领域相关的接口。
- Client:与符合Target接口的对象协同。
- Adaptee:定义一个已经存在的接口,这个接口需要适配。
- Adapter:对Adapter的接口与Target接口进行适配
3. 案例分析
有时,为复用而设计的工具箱不能够被复用的原因仅仅是因为它的接口与专业应用领域所需要的接口匹配。
例如,有一个绘图编辑器,这个编辑器允许用户绘制和排列基本图元(线、多边形和正文等)生成图片和图表。这个绘图编辑器的关键抽象是图形对象。图形对象有一个可编辑形状,并可以绘制自身。图形对象的接口由一个称为Shape的抽象类定义,绘图编辑器为每种图形对象定义了一个Shape的子类:LineShape类对应于直线,PolygonShape类对应于多边形,等等。
像LineShape和PolygonShape这样的基本几何图形的类比较容易实现,这是由于它们的绘图和编辑功能本来就很有限。但是对于可以显示和编辑正文的TextShape子类来说,实现相当困难,因为即使是基本的正文编辑器也要涉及到复杂的屏幕刷新和缓冲区管理。同时,成品的用户界面工具箱已经提供了一个复杂的TextView类用于显示和编辑正文。理想的情况是我们可以复用这个TextView类以实现TextShape类,但是工具箱的设计者当时并没有考虑Shape的存在,因此TextView和Shape对象不能互换。
一个应用可能会有一些类具有不同接口并且这些接口互不兼容,在这样的应用中像TextView这样已经存在并且不相关的类如何协调工作呢?我们可以改变TextView类使它兼容Shape类的接口,单前提是必须有这个工具箱的源代码。然而即使我们得到了这些源代码,修改TextView也是没有什么意义的;因为不应该仅仅为了实现一个应用,工具箱就不得不采用一些与特定领域相关的接口。
我们可以不用上面的方法,而定义一个TextShape类,由它来适配TextView的接口和Shape的接口。我们可以用两种方法做这件事:1)继承Shape类的接口和TextView的实现,或2)将一个TextView实例作为TextShape的组成部分,并且使用TextView的接口实现TextShape。这两种方法恰恰对应于Adapter模式的类和对象版本。我们将TextShape称之为适配器Adapter。
类适配器和对象适配器有不同的权衡。类适配器
- 用一个具体的Adapter类对Adaptee和Target进行匹配。结果是当我们想要匹配一个类以及所有它的子类时,类Adapter将不能胜任工作。
- 使得Adapter可以重定义Adaptee的部分行为,因为Adapter是Adaptee的一个子类。
- 仅仅引入了一个对象,并不需要额外的指针以间接得到adaptee。
对象适配器则
- 允许一个Adapter与多个Adaptee——即Adaptee本身以及它的所有子类(如果子类有的话)一同工作。Adapter也可以一次给所有的Adaptee添加功能。
- 使得重定义Adaptee的行为比较困难。这就需要生成Adaptee的子类并且使得Adapter引用这个子类而不是引用Adaptee本身。
类适配器采用多重继承适配接口:
/*********************************************************************************
*Copyright(C),Your Company
*FileName: include.h
*Author: Huangjh
*Version:
*Date: 2017-11-06
*Description: 通用的头文件
*Others:
**********************************************************************************/
#ifndef _INCLUDE_H
#define _INCLUDE_H
struct Point
{
unsigned int m_uiX;
unsigned int m_uiY;
};
#endif //#ifndef _INCLUDE_H
/*********************************************************************************
*Copyright(C),Your Company
*FileName: Shape.h
*Author: Huangjh
*Version:
*Date: 2017-11-06
*Description: 相当于结构图中的Target角色:定义Client使用的与特定领域相关的接口
*Others:
**********************************************************************************/
#ifndef _SHAPE_H_
#define _SHAPE_H_
#include "include.h"
class CShape
{
public:
CShape() { }
virtual ~CShape() { }
virtual void DrawShapeArea(struct Point &bottomLeft, struct Point &topRight) const = 0;
};
#endif //#ifndef _SHAPE_H_
/*********************************************************************************
*Copyright(C),Your Company
*FileName: TextView.h
*Author: Huangjh
*Version:
*Date: 2017-11-06
*Description: 相当于结构图中的Adaptee角色:定义一个已经存在的接口,这个接口需要适配
*Others:
**********************************************************************************/
#ifndef _TEXT_VIEW_H
#define _TEXT_VIEW_H
#include <iostream>
#include "include.h"
class CTextView
{
public:
CTextView() { }
virtual ~CTextView() { }
virtual void DrawTextArea(struct Point &Origin, unsigned int uiWidth, unsigned int uiHeight) const
{
std::cout << "成品的工具箱——绘制一个复杂的文本区域用于文本显示" << std::endl;
}
};
#endif //#ifndef _TEXT_VIEW_H
/*********************************************************************************
*Copyright(C),Your Company
*FileName: TextShape.h
*Author: Huangjh
*Version:
*Date: 2017-11-06
*Description: 相当于结构图中的Adapter角色:对Shape和TextView接口进行适配
*Others:
**********************************************************************************/
#ifndef _TEXT_SHAPE_H_
#define _TEXT_SHAPE_H_
#include "Shape.h"
#include "TextView.h"
#include "include.h"
class CTextShape : public CShape, private CTextView
{
public:
CTextShape() { }
~CTextShape() { }
void DrawShapeArea(struct Point &bottomLeft, struct Point &topRight) const
{
unsigned int uiWidth = topRight.m_uiX - bottomLeft.m_uiX;
unsigned int uiHeight = topRight.m_uiY - bottomLeft.m_uiY;
DrawTextArea(bottomLeft, uiWidth, uiHeight);
}
};
#endif //#ifndef _TEXT_SHAPE_H_
/*********************************************************************************
*Copyright(C),Your Company
*FileName: main.cpp
*Author: Huangjh
*Version:
*Date: 2017-11-06
*Description: 类适配的测试用例
*Others:
**********************************************************************************/
#include "Shape.h"
#include "TextShape.h"
int main(void)
{
struct Point bottomLeft = { 10, 10 };
struct Point topRight = { 20, 30 };
CShape *pShape = new CTextShape();
pShape->DrawShapeArea(bottomLeft, topRight);
return 0;
}
运行结果如下所示:
成品的工具箱——绘制一个复杂的文本区域用于文本显示
对象适配器采用对象组合的方法将具有不同接口的类组合在一起。
/*********************************************************************************
*Copyright(C),Your Company
*FileName: TextShape.h
*Author: Huangjh
*Version:
*Date: 2017-11-06
*Description: 相当于结构图中的Adapter角色:对Shape和TextView接口进行适配
*Others:
**********************************************************************************/
#ifndef _TEXT_SHAPE_H_
#define _TEXT_SHAPE_H_
#include "Shape.h"
#include "TextView.h"
#include "include.h"
class CTextShape : public CShape
{
public:
CTextShape()
:m_pTextView(new CTextView())
{
}
~CTextShape()
{
if (m_pTextView != NULL)
delete m_pTextView;
}
void DrawShapeArea(struct Point &bottomLeft, struct Point &topRight) const
{
unsigned int uiWidth = topRight.m_uiX - bottomLeft.m_uiX;
unsigned int uiHeight = topRight.m_uiY - bottomLeft.m_uiY;
if (m_pTextView != NULL)
m_pTextView->DrawTextArea(bottomLeft, uiWidth, uiHeight);
}
private:
CTextView *m_pTextView;
};
#endif //#ifndef _TEXT_SHAPE_H_
4. 适用性
在以下情况下使用Adapter模式
- 你想使用一个已经存在的类,而它的接口不符合你的需求。
- 你想创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类(即那些接口可能不一定兼容的类)协同工作。
- (仅适用于对象Adaper)你想使用一些已经存在的子类,但是不可能对每一个都进行子类化以匹配它们的接口。对象适配器可以适配它的父类接口。
5. 参考资料
- 《大话设计模式》
- 《设计模式——可复用面向对象软件的基础》