设计模式(9) 装饰器模式(DECORATOR)

问题聚焦:   
    有时候,希望某个类增加新功能的时候,往往会生成一个子类。
    但是,这种方式有时候略显臃肿,其次,这种方法是静态的,不够灵活。
    因此,针对这种给对象动态添加功能的需求,大牛们给出了本节的装饰器模式。


意图:
    动态地给一个对下添加一些额外的职责。
    就增加功能来说,装饰器模式比相比生成子类更加灵活。

别名:
    包装器Wraper

动机:
    给某个对象,而不是整个类,添加一些功能。
    举一个例子:
    一个图形用户界面工具箱。
    要求:允许你对任意一个用户界面添加一些特性,如边框,或者一些行为,如窗口滚动。
    继承机制:使用继承机制是添加功能的一种有效途径,从其他类继承过来的边框特性可以被多个子类的实例所使用。
    继承机制的缺点:不够灵活,因为边框的选择是静态的,用户不能控制对组件加边框的方式和时机。
    装饰器方式:将组件嵌入到另一个对象中,由这个对象添加边框。我们称这个嵌入的对象为装饰。
    装饰器方式的特点:这个装饰与它所装饰的组件接口一致,因此它对使用该组件的客户透明。
    装饰器方式的优点:透明性。它将客户请求转发给该组件,并且可能在转发前后执行一些额外的动作。同时,你可以递归地嵌套多个装饰,从而可以添加任意多的功能。
    Demo:假定有一个对象TextView,它可以在窗口中显示正文。
    需求:在需要的时候,添加滚动条(使用ScrollDecorator),或者添加一个粗黑边框(使用BorderDecorator)。
    类之间的关系:
    设计模式(9)  装饰器模式(DECORATOR)设计模式(9)  装饰器模式(DECORATOR)
    装饰的过程如下图所示:
    设计模式(9)  装饰器模式(DECORATOR)设计模式(9)  装饰器模式(DECORATOR)
    可视对象的装饰器类图设计:
    设计模式(9)  装饰器模式(DECORATOR)设计模式(9)  装饰器模式(DECORATOR)
    VisualComponent描述可视对象的抽象类,定义了绘制和事件处理的接口。
    Decorator的子类为特定功能可以*地添加一些操作。
    优点:客户通常不会感觉到装饰过的组件与未装饰组件之间的差异,也不会与装饰产生任何依赖关系。因为装饰器是被界面的其他对象所使用。而并不是客户直接使用。


适用性:
  • 一下情况使用装饰器模式:
  • 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责
  • 处理那些可以撤销的职责
  • 当不能采用生成子类的方法进行扩充时。不能采用子类的情况有两种:可能有大量独立的扩展,为支持每一种扩展,导致子类数目爆炸性增长。另一种情况,类定义被隐藏,或类定义不能用于生成子类。
结构:
设计模式(9)  装饰器模式(DECORATOR)
设计模式(9)  装饰器模式(DECORATOR)
参与者:
  • Component(VisualComponent):定义一个对象接口,可以给这些对象动态地添加职责
  • ConcreteComponent(TextView):定义一个对象,可以给这个对象添加一些职责
  • Decotator:维持一个指向Component对象的指针,并定义一个与Component接口一致的接口
  • ConcreteDecorator(BorderDecorator,ScrollDecorator):向组件添加职责
协作:
Decorator将请求转发给它的Component对象,并可能在钻发请求前后执行一些附加的动作。

效果:
  • 比静态继承更灵活:用添加和分离的方法,用装饰器在运行时刻增加和删除职责。
  • 避免在层次结构高层的类有太多的特征:装饰器不不试图在一个复杂可定制的类上支持,而是倾向于在简单的类上添加一系列可选的功能。
  • Decorator与它的Component不一样:装饰时,不依赖被装饰对象的一些特征。
  • 有许多小对象:会有许多看上去类似的小对象,这些对象仅仅在他们的相互连接的方式上有所不同。这是装饰器的缺点。
实现:
使用装饰器模式时应注意以下几点:
  • 接口的一致性:装饰器对象的接口必须与它所装饰的Component的接口是一致的。
  • 省略抽象的Decorator模式:当仅需要添加一个职责时,没有必要定义抽象Decorator类。
  • 保持Component类的简单性:为保证接口的一致性,组件和装饰必须有一个公共的Component父类,因此保持这个类的简单性是很重要的。
  • 改变对象外壳与改变对象内核:装饰器就想对象的外壳,改变的是对象的行为;而当被装饰对象过于庞大和复杂时,装饰器就显得力不从心了,这时候,后面会介绍的Strategy模式可能是一个更好的选择。

代码示例:
下面的示例,说明如何实现一个装饰器。
// 假定已经存在一个Component类VisualComponent
class VisualComponent
{
public:
    VisualComponent();
    
    virtual void Draw();
    virtual void Resize();
    //... ...
};

// 我们定义VisualComponent的一个子类Decorator,将生成Decorator的子类以获取不同的装饰
class Decorator  : public VisualComponent
{
public:
    Decorator(VisualComponent*);

    virtual void Draw();
    virtual void Resize();
    // ......
private:
    VisualComponent* _component;    // 这个示例变量在构造器中被初始化,通过这个变量,进行请求的转发
};

// 对于VisualComponent接口中定义的每一个操作,Decorator类都定义了一个缺省的实现,这一实现将请求转发给_component
void Decorator::Draw()
{
    _component->Draw();
}

void Decorator::Resize()
{
    _component->Resize();
}

// Decorator的子类定义了特殊的装饰功能
class BorderDecorator : public Decorator
{
public:
    BorderDecorator(VisualComponent*, int borderWidth);
    virtual void Draw();
private:
    void DrawBorder(int);
private:
    int _width;
};

void BorderDecorator::Draw()
{
    Decorator::Draw();
    DrawBorder(_width);
}
// 类似的可实现ScrollDecorator和DropShadowDecorator,给界面添加滚动和阴影功能

下面的代码展示了如何使用装饰器创建一个具有边界的可滚动的TextView
//首先将一个可视组件放入窗口对象中,这个操作为Window中的SetContents操作
void Window::SetContents(VisualComponent* contents) 
{
    // ......
}

// 现在创建一个正文视图,放入窗口中
Window* window = new Window;
TextView* textView = new TextView;
window->SetContents(textView);

// 如果想要增加滚动条,我们需要在将它放入窗口之前进行装饰
window->SetContents(
    new BorderDecorator(
        new ScrollDecorator(textView), 1
        )
    );


相关模式:
适配器模式给予对象一套全新的接口,而装饰器模式并不改变对象的接口,只改变对象的职责
组合模式:可以将装饰器模式视为一个退化的,仅有一个组件的组合。
Strategy模式:用一个装饰器可以改变对象的外表,而Strategy模式使得你可以改变对象的内核。这时动态改变对象的两种途径。


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


设计模式(9) 装饰器模式(DECORATOR)

上一篇:在mybatis中遇到奇怪的问题


下一篇:UiButton操作