设计模式(6) 结构型模式和适配器模式(ADAPTER)

问题聚焦:
结构型模式:对象组合
适配器模式:使得原本由于接口不兼容而不能一起工作的那些类可以一起工作

结构型模式
定义:描述了如何对一些对象进行组合,从而实现新功能的一些方法。
特点:可以在运行时刻改变对象结合关系。
包括:
  1. Adapter:使得被兼容接口与其他接口兼容,从而给出了多个不同接口的统一抽象(第一次使用模式就是适配器模式)
  2. Composite:描述了如何构造一个类层次结构,这一结构由两种类型的对象(基元对象和组合对象)所对应的类构成。
  3. Proxy:proxy对象作为其他对象的一个方便的替代或占位符,还可以对对象的一些特有性质的一定程度的间接访问,从而可以限制、增强或修改这些性质
  4. Flyweight:为共享对象定义了一个结构,如何生成很多较小的对象
  5. Facade:描述了如何用单个对象表示整个子系统
  6. Bridge:将对象的抽象和实现分离,从而可以独立地改变它们
  7. Decorator:描述了如何动态地为对象添加职责,递归组合对象,从而允许你添加任意多的对象职责

适配器模式
意图:将一个类的接口转换成客户希望的另一个接口。
功能:使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
别名:包装器Wrapper
动机:
Demo 绘图编辑器
功能:允许用户绘制和排列基本图元生成图片和图表
关键抽象:图形对象,图形对象有一个可编辑的形状,并可以绘制自身
接口:Shape抽象类
设计:绘图编辑器为每一种图形对象定义了一个Shape子类:LineShape类对应于直线,PolygonShape类对应于多边型。
难点:成品的用户界面工具箱可能已经提供了一个复杂的TextView类,用于显示和编辑正文,但是并没有Shape类,所以TextView类和Shape类不能互换
方法:定义一个TextShape类,来适配TextView的接口和Shape的接口
实现:方式1 继承Shape类的接口和TextView的实现(类适配器)  
           方式2 将一个TextView实例作为TextShape的组成部分,并且使用TextView的接口实现TextShape。(对象适配器)
结构:对象适配器方式

注意:Adpater时常还要负责提供那些被匹配的类所没有提供的功能。
适用性:
以下情况考虑使用Adapter模式:
  • 使用一个已经存在存在的类,而它的接口不符合你的需求
  • 创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类协同工作
  • 使用一些已经存在的子类,但是不可能对每一个都进行子类化以匹配它们的接口,对象适配器可以适配它的父类接口
结构:
1 类适配器:使用多重继承对一个接口和另一个接口进行匹配


2 对象匹配器:依赖于对象组合


参与者:
  • Target(Shape):定义Client使用的与特定领域相关的接口
  • Client(DrawingEditor):与符合Target接口的对象协同
  • Adaptee(TextView):定义一个已经存在的接口,这个接口需要适配
  • Adapter(TextShape):对Adaptee的接口与Target接口进行适配
协作:Client在Adapter实例上调用一些操作,接着适配器调用Adapter的操作实现这个请求
效果:
类适配器:
  • 用一个具体的Adapter类对Adaptee和Target进行匹配,结果是当我们想要匹配一个类及它的子类时,类Adapter不能胜任工作
  • Adapter类可以重定义部分Adaptee的行为
  • 仅仅引入了Adapter对象,并不需要得到Adaptee类的实例
对象适配器
  • 允许一个Adapter和Adaptee及其子类同时工作,Adapter也可以一次给所有的Adaptee添加功能
  • 使得重定义Adaptee的行为比较困难,需要生成Adaptee的子类并且使得Adapter引用这个子类而不是引用Adaptee本身
考虑的因素:
  • Adapter的匹配程度:对Adaptee的接口与Target接口进行匹配的工作量取决于Target接口与Adaptee接口的相似程度
  • 可插入的Adapter:如果将接口匹配构建为一个类,就不需要假定对其他的类课件的是一个相同的接口,就可以将我们自己的类加入到现有的系统中(在实现部分将具体讨论构建接口适配的方法)
  • 使用双向适配器提供透明操作:一个适配器可能被不同的客户使用,而使用方式不尽相同,这时候就需要同时支持这样的透明性(通常多继承是个好方法)。
实现:
使用C++实现适配器类:在使用C++实现适配器类时,Adapter类应该采用公共方式继承Target类,并且用私有方式继承Adaptee类,因此Adapter类应该是Target的子类型,但不是Adaptee的子类型。
可插入的适配器的实现:
Demo:在一个目录结构中,GetSubdirectories操作进行访问子目录,在一个继承层次结构中,相应的操作为GetSubclasses。一个复用的TreeDisplay窗口组件能够兼容这两种操作,即具有接口适配的功能。
实现途径:
a) 使用抽象操作:在TreeDisplay类中定义窄Adaptee接口相应的抽象操作,这样就由子类来实现这些抽象操作并匹配具体的树结构的对象
DirectoryTreeDisplay对接口以特化,使得它的DirectoryBrower客户可以用它来显示目录结构。

b) 使用代理对象:TreeDisplay有一个代理对象,客户进行一些选择,并将这些选择提供给代理对象,这样客户就可以对适配加以控制。
将TreeDisplay需要定义的接口放到纯虚函数TreeAccessorDelegate中,然后运用继承机制将这个接口融合到我们所选择的代理中。

c) 参数化适配器:用一个或多个模块对适配器进行参数化,一个模块可以匹配一个请求,并且适配器可以为每个请求存储一个模块。

代码示例
在“动机”部分描述的例子作为代码示例的背景。在这里分别用类适配器和对象适配器实现代码的简要框架。
类设定:
class Shape {
public:
    Shape();
    virtual void BoundingBox(Point& bottomLeft, Point& topRight) const;
    virtual Manipulator* CreateManipulator() const;
};

class TextView {
public:
    TextView();
    void GetOrigin(Coord& x, Coord& y) const;
    void GetExtent(Coord width, Coord& height) const;
    virtual bool isEmpty() const;
};

Shape假定是一个边框,这个边框由它相对的两个角定义。
TextView则由原点,宽度和高度定义。TextShape是不同接口间的适配器。
Shape同时定义了CreateManipulator操作用于创建一个Manipulator对象。当用户操作一个图形时,Manipulator对象知道如何驱动这个图习惯。

类适配器采用多重继承适配接口。
类适配器的关键是用一个分支继承接口,而用另外一个分支继承接口的实现部分。通常C++中的做法是:用公共方式继承接口,用私用方式继承接口的实现。
Class TextShape : public Shape, private TextView 
{
public:
    TextShape();
    virtual void BoundingBox (Point& bottomLeft, Point& topRight) const;
    virtual bool isEmpty() const;
    virtual Manipulator* CreateManipulator() const;
};

// BoundingBox操作对TextView的接口进行转换,使之适配Shape的接口
void TextShape::BoundingBox ( Point& bottomLeft, Point& topRight) const
{
    Coord bottom , left, width, height;

    GetOrigin(bottom, left);
    GetExtent(width, height);

    bottomLeft = Point(bottom, left);
    topRight = Point(bottom + height, left + width);
}

// IsEmpty操作给出了在适配器实现过程中常用的一种方法:直接转发请求
bool TextShape::IsEmpty() const 
{
    return new TextManipulator(this);
}

对象适配器采用对象组合的方法,将具有不同接口的类组合在一起。
该方法中,适配器TextShape维护一个指向TextView的指针。
class TextShape : public Shape 
{
public:
    TextShape(TextView*);

    virtual void BoundingBox(Point& bottomLeft, Point& topRight) const;
    virtual bool IsEmpty() const;
    virtual Manipulator* CreateManipulator() const;
private:
    TextView* _text;
};

// 在构造函数中对TextView实例的指针进行初始化
TextShape::TextShape (TextView* t)
{
    _text = t;
}

void TextShape::BoundingBox (Point& bottomLeft, Point& topRight) const
{
    Coord bottom, left, width, height;
    
    _text->GetOrigin(bottom, left);
    _text->GetExtent(width, height);

    bottomLeft = Point(botto, left);
    topRight = Point(bottom + height, left + width);
}

bool TextShape::IsEmpty () const 
{
    return _text->IsEmpty();
}

// CreateManipulator的实现代码与类适配器版本的实现代码一样。
Manipulator* TextShape::CreateManipulator () const
{
    return new TextManipulator(this);
}


相关模式
Bridge模式的结构与对象适配器类似,但是Bridge模式的出发点不同:
  • Bridge目的是将接口部分和实现部分分离,从而对它们可以较为容易地相对独立的加以改变
  • Adapter则意味着改变一个已有对象的接口
Decorator模式增强了其他对象的功能,而同时又不改变它的接口。Decorator支持递归组合,而适配器是不能实现这一点的。
Proxy模式在不改变它的接口的条件下,为另一个对象定义了一个代理。


参考资料:
《设计模式:可复用面向对象软件的基础》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值