情景:接到一个新任务,需求是实现一个简单的绘图编辑器,工具需要基本满足绘制常用图元(线,多边形,正文等)以生成图片和图表。
分析:绘图编辑器的关键抽象是图形对象,图形对象需要满足能够绘制自身,且拥有一个可编辑的形状(绘制时的状态保存)。常见的,图形对象的接口可以由一个称为Shape的抽象类定义,那么接下来我们便可以对每一种具象的图像对象定义成Shape的子类,比如直线可以是LineShape,多边形可以是PolygonShape等。
问题:现在看来一切都在正常进行的,LineShape跟PolygonShape的工作得很好,基本满足了我们得需求,但当我们开始着手“正文”图形对象时,问题出现了,因为即使是基本的正文编辑也要涉及到复杂的屏幕刷新和缓冲区管理,同时,成品的用户工具箱可能已经提供了一个复杂的TextView类用于显示和编辑正文,一种可行的做法是使用TextView类兼容Shape的接口以实现TextShape类,这看起来不错,但事实上我们不应该仅仅为了实现一个应用(绘图编辑器),就让成品工具箱内的TextView去采用一些与特定领域相关的接口。
解决方案:使用TextShape来适配TextView跟Shape接口(在不改变TextView的类结构的情况下),我们可以有两种方法做这件事:
1.TextShape继承Shape接口跟TextView的实现,
2.将整个TextView实例作为TextShape的组成部分,并且使用TextView接口实现TextShape。
这两种方法其实就是Adapter模式的类和对象版本,我们将TextShape称之为适配器Adapter。
上图主要说明了在Shape类中声明的BoundingBox请求(获取边框两点坐标)是如何被转换成TextView类中定义的GetExtent请求(获取Text长和宽)+GetOrigin请求(获取Text左下坐标-->原点),这样TextShape将TextView接口与Shape接口进行了匹配,绘图编辑器就可以复用原先不兼容的TextView类。此外,Shape对象提供CreateManipulator请求,Manipulator是一个抽象类,其知道如何驱动Shape类响应用户的各类操作,例如将图形拖动到一个新的位置。Manipulator有不同的子类,例如子类TextManipulator对应TextShape。
关键字:
Target--定义Client使用的与特定领域相关的接口(Shape)
Client--与符合Target接口的对象协同(绘图编辑器)
Adaptee--定义一个已经存在的接口,这个接口需要适配(TextView)
Adpater--对Adaptee接口与Target接口进行适配(TextShape)
实现:
struct Point
{
public:
Point(int x, int y) :m_iX(x), m_iY(y) {};
int m_iX;
int m_iY;
};
class Manipulator
{
public:
Manipulator(Shape *pShape) :m_pShape(pShape) {};
virtual ~Manipulator();
virtual bool MoveToPos(const Point &pointStart, const Point &pointEnd) = 0;
private:
Shape *m_pShape;
};
class Shape
{
public:
Shape();
virtual void BoundingBox(Point &bottomLeft, Point &topRight) const;
virtual Manipulator *CreateManipulator();
};
class TextView
{
public:
TextView();
void GetOrigin(int &x, int &y) const;
void GetExtent(int &width, int &height) const;
virtual bool IsEmpty() const;
};
class TextManipulator :public Manipulator
{
public:
TextManipulator(Shape *pShape);
virtual ~TextManipulator();
virtual bool MoveToPos(const Point &pointStart, const Point &pointEnd);
};
类适配器模式(多继承适配)
class TextShape : public Shape, private TextView
{
public:
TextShape();
virtual void BoundingBox(Point &bottomLeft, Point &topRight) const;
virtual bool IsEmpty() const;
virtual Manipulator *CreateManipulator();
};
void TextShape::BoundingBox(Point &bottomLeft, Point &topRight) const
{
int x, y, width, height = 0;
GetOrigin(x, y);
GetExtent(width, height);
bottomLeft = Point(x, y);
topRight = Point(x + width, y + height);
}
bool TextShape::IsEmpty() const
{
return TextView::IsEmpty();
}
Manipulator *TextShape::CreateManipulator()
{
return new TextManipulator(this);
}
对象适配器模式(对象组合)
class TextShape : public Shape
{
public:
TextShape(TextView *textView);
virtual void BoundingBox(Point &bottomLeft, Point &topRight) const;
virtual bool IsEmpty() const;
virtual Manipulator *CreateManipulator();
private:
TextView *m_pTextView;
};
TextShape::TextShape(TextView *textView)
{
m_pTextView = textView;
}
void TextShape::BoundingBox(Point &bottomLeft, Point &topRight) const
{
int x, y, width, height = 0;
m_pTextView->GetOrigin(x, y);
m_pTextView->GetExtent(width, height);
bottomLeft = Point(x, y);
topRight = Point(x + width, y + height);
}
bool TextShape::IsEmpty() const
{
return m_pTextView->IsEmpty();
}
Manipulator *TextShape::CreateManipulator()
{
return new TextManipulator(this);
}
适用性:
- 你想使用一个已经存在的类,而它的接口不符合你的要求(TextView)
- 你想使用一些已经存在的子类,但是不可能对每一个都进行子类化以匹配它们的接口,对象适配器模式可以适配它们的父类接口
选择:
选择类适配器与对象适配器有不同的权衡。
类适配器
- 使得Adapter可以重定义Adaptee的部分行为,因为Adapter是Adaptee的一个子类(多继承)。
- 不需要额外的指针以间接得到Adaptee。
对象适配器
- 允许一个Adapter与多个Adaptee--即Adaptee本身以及它的所有子类(如果有的话)一同工作。也可以一次给所有的Adaptee添加功能。
应用:
1.在C++ STL中广泛使用了Adapter模式,主要有container adapter、iterator adapter、functor adapter:
*container adapter: stack, queue(数据结构)
*iterator adapter: front_insert_iterator, back_insert_iterator, istream_iteator, ostream_iterator
*functor adapter: bind, negate, compose (与对一般函数或者成员函数的修饰)
2.Android ListView中Adapter模式
等等