适配器模式分为两种:类适配器模式和对象适配器模式。
类适配器模式需要用到多重继承机制(C++支持)。
然而Java/C#等语言不支持多重继承,那么可以采用对象适配器模式。
本文首先讲解类适配器模式在C++中的实现,然后讲解对象适配器模式在Java中的实现。
背景
我们有一个绘图应用,可以在屏幕上绘制一些形状。该应用首先会获得该形状占据的区域大小,然后将形状绘制在此区域内。
客户端代码:
int main(int argc, char** argv) { Shape* shape = new Shape(); //获取该形状占据的矩形区域,以便于确定在哪个区域绘制该形状 Rect* boundingBox = shape->BoundingBox(); //绘制该形状 /****/ return 0; }
现在我们引用了一个第三方库,里面有TextView类。
我们希望可以把TextView绘制在屏幕上,可是TextView类没有BoundingBox方法,因此我们不知道TextView占据的区域形状也就无法直接绘制该类。解决方案如下:
- 修改TextView类的代码,以增加BoundingBox方法。(不可行,我们可能无法获得源码)
- 用适配器模式。(对,下面我们来详细讲解)
适配器模式
适配器模式就像手机适配器(手机充电器)可以把220V的交流电转换为手机可以直接使用的5V直流电。我们先介绍下适配器模式中的几个术语:
- 目标类(Target):5V直流电
- 适配者(Adaptee): 220V交流电
- 适配器(Adapter):手机充电器
我们知道客户端已经可以在屏幕上绘制Shape对象。然而不知道如何绘制TextView对象,因为它没有BoundingBox方法。我们考虑新建一个TextShape类,其含有BoundingBox方法。
Shape是目标类(Target)。
TextView是适配者(Adaptee)。
TextShape是适配器(Adapter)。
(图为类适配器模式)
(图为对象适配器模式)
C++实现代码(类适配器模式)
可以想象,适配器模式的关键是Adapter如何将Adaptee转换为Targe。如何转换取决于具体的应用。
Adapter代码:
Rect* TextShape::BoundingBox() const { //一个中文字符占用2个相对宽度 //一个英文字符占用1个相对宽度 //这里为了简单起见,一律认为是1个相对宽度 float width = this->GetText().length(); float heigh = 1.0; //高度和宽度都要乘上字体大小比例 return new Rect( new Point(0, 0), new Point(width * this->GetFontSize(), heigh * this->GetFontSize()) ); }
客户端代码:
//采用类适配器模式的话,客户端代码会比较容易 //如果采用对象适配器模式,客户端的代码会多一点 int main() { Shape* shape = new TextShape(); Rect* boundingBox = shape->BoundingBox(); return 0; }
Java实现代码(对象适配器模式)
适配器代码:
//由于不支持多继承 //需要在构造函数传入adaptee对象 public class TextShape extends Shape{ private TextView textView; public TextShape(TextView textView) { this.textView = textView; } @Override public Rectangle boundingBox() { //根据textView的内容计算占据的区域大小 //这里需要较复杂的代码 return null; } }
客户端代码:
public class Client { public static void main(String[] args) { TextView textView = new TextView(); Shape shape = new TextShape(textView); shape.boundingBox(); } }