1. 概述
有时我们希望给某个对象而不是整个类添加一些功能。例如,一个图形用户界面工具箱允许你对任意一个用户界面添加一些特性,例如边框,或是一些行为,例如窗口滚动。
使用继承机制是添加功能的一种有效途径,从其他类继承过来的边框特性可以被多个子类的实例所使用。但这种方法不够灵活,因为边框的选择是静态的,用户不能控制对组件加边框的方式和时机。
一种较为灵活的方式是将组件嵌入另一个对象中,由这个对象添加边框。我们称这个嵌入的对象为
装饰。这个装饰与它所装饰的组件接口一致,因此它对使用该组件的客户透明。它将客户请求转发给该组件,并且可能在转发前后执行一些额外的动作(例如画一个边框)。透明性使得你可以递归的嵌套多个装饰,从而可以添加任意多的功能。
装饰模式(Decorator),动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活。
2. 结构图
下面是装饰模式的结构图:
- Component:定义一个对象接口,可以给这些对象动态的添加职责。
- ConcreteComponent:定义一个对象,可以给这个对象添加一些职责。
- Decorator:维持一个指向Component 对象的指针,并定义一个与Component 接口一致的接口。
- ConcreteDecorator:向组件添加职责,
Decorator将请求转发给它的Component对象,并且可能在转发请求后执行一些附加的动作。
3. 实例分析
考虑这么一个应用场景:假定有一个对象TextView,它可以在窗口中显示正文。缺省的TextView没有滚动条,因为我们可能有时候不需要滚动条。当需要滚动条时,我们可以用ScrollDecorator添加滚动条。如果我们还想在TextView周围添加一个粗黑边框,可以使用BorderDecorator添加。因此只要简单地将这些装饰和TextView进行组合没救可以达到预期的效果。
/*********************************************************************************
*Copyright(C),Your Company
*FileName: VisualComponent.h
*Author: Huangjh
*Version:
*Date: 2017-11-13
*Description: 相当于结构图中的Component角色——定义一个对象接口
*Others:
**********************************************************************************/
#ifndef _VISUAL_COMPONENT_H
#define _VISUAL_COMPONENT_H
class CVisualComponent
{
public:
CVisualComponent() { }
virtual ~CVisualComponent() { }
virtual void Draw(void) = 0;
};
#endif //#ifndef _VISUAL_COMPONENT_H
/*********************************************************************************
*Copyright(C),Your Company
*FileName: TextView.h
*Author: Huangjh
*Version:
*Date: 2017-11-13
*Description: 相当于结构图中的ConcreteComponent角色——定义一个对象,我们的目的就是
给这个对象添加一些职责
*Others:
**********************************************************************************/
#ifndef _TEXT_VIEW_H
#define _TEXT_VIEW_H
#include <iostream>
#include "VisualComponent.h"
class CTextView : public CVisualComponent
{
public:
CTextView() { }
~CTextView() { }
void Draw(void)
{
std::cout << "在窗口中显示正文" << std::endl;
}
};
#endif //#ifndef _TEXT_VIEW_H
/*********************************************************************************
*Copyright(C),Your Company
*FileName: Decorator.h
*Author: Huangjh
*Version:
*Date: 2017-11-13
*Description: 相当于结构图中的Decorator角色——维持一个指向Component对象的指针
并定义一个与Component接口一致的接口
*Others:
**********************************************************************************/
#ifndef _DECORATOR_H
#define _DECORATOR_H
#include <cstddef>
#include "VisualComponent.h"
class CDecorator : public CVisualComponent
{
public:
CDecorator(CVisualComponent *pComponent)
:m_pComponent(pComponent)
{
}
virtual ~CDecorator() { }
virtual void Draw(void)
{
if (m_pComponent != NULL)
m_pComponent->Draw();
}
private:
CVisualComponent *m_pComponent;
};
#endif //#ifndef _DECORATOR_H
/*********************************************************************************
*Copyright(C),Your Company
*FileName: ScrollDecorator.h
*Author: Huangjh
*Version:
*Date: 2017-11-13
*Description: 相当于结构图中的ConcreteDecorator角色——向组件添加一个滚动条的职责
*Others:
**********************************************************************************/
#ifndef _SCROLL_DECORATOR_H
#define _SCROLL_DECORATOR_H
#include <iostream>
#include "Decorator.h"
class CScrollDecorator : public CDecorator
{
public:
CScrollDecorator(CVisualComponent *pComponent, int iWidth)
:CDecorator(pComponent)
, m_iWidth(iWidth)
{
}
~CScrollDecorator() { }
void Draw(void)
{
CDecorator::Draw();
DrawScroll();
}
private:
void DrawScroll(void)
{
std::cout << "绘制一个宽度为" << m_iWidth << "的滚动条" << std::endl;
}
int m_iWidth;
};
#endif //#ifndef _SCROLL_DECORATOR_H
/*********************************************************************************
*Copyright(C),Your Company
*FileName: BorderDecorator.h
*Author: Huangjh
*Version:
*Date: 2017-11-13
*Description: 相当于结构图中的ConcreteDecorator角色——向组件添加一个边框的职责
*Others:
**********************************************************************************/
#ifndef _BORDER_DECORATOR_H
#define _BORDER_DECORATOR_H
#include <iostream>
#include "Decorator.h"
class CBorderDecorator : public CDecorator
{
public:
CBorderDecorator(CVisualComponent *pComponent, int iWidth)
:CDecorator(pComponent)
,m_iWidth(iWidth)
{
}
~CBorderDecorator() { }
void Draw(void)
{
CDecorator::Draw();
DrawBorder();
}
private:
void DrawBorder(void)
{
std::cout << "绘制一个宽度为" << m_iWidth << "的边框" << std::endl;
}
int m_iWidth;
};
#endif //#ifndef _BORDER_DECORATOR_H
/*********************************************************************************
*Copyright(C),Your Company
*FileName: main.cpp
*Author: Huangjh
*Version:
*Date: 2017-11-13
*Description: 装饰模式的测试用例
*Others:
**********************************************************************************/
#include "TextView.h"
#include "ScrollDecorator.h"
#include "BorderDecorator.h"
int main(void)
{
CVisualComponent *pTextView = new CTextView();
CVisualComponent *pScrollDecorator = new CScrollDecorator(pTextView, 2);
CVisualComponent *pBorderDecorator = new CBorderDecorator(pScrollDecorator, 3);
pBorderDecorator->Draw();
return 0;
}
运行结果如下所示:
在窗口中显示正文
绘制一个宽度为2的滚动条
绘制一个宽度为3的边框
当你仅需要添加一个职责时,没有必要定义抽象CDecorator类。你常常需要处理现存的类层次结构而不是设计一个新系统,这时你可以把CDecorator向Component转发请求的职责合并到ConcreteDecorator中。
4. 优缺点
4.1 优点
- 比静态继承更灵活 与对象的静态继承(多重继承)相比,Decorator模式提供了更加灵活的向对象添加职责的方式。可以用添加和分离的方法,用装饰在运行时刻增加和删除职责。相比之下,继承机制要求为每个添加的职责创建一个新的子类。这会产生许多新的类,并且会增加系统的复杂度。此外,为一个特定的Component类提供多个不同的Decorator类,这就使得你可以对一些职责进行混合和匹配。
- 避免在层次结构高层的类有太多的特征 Decorator模式提供了一种“即用即付”的方法来添加职责。它并不试图在一个复杂的可定制的类中支持所有可预见的特征,相反,你可以定义一个简单的类,并用Decorator类给它逐渐地添加功能。可以从简单的部件组合出复杂的功能。
4.2 缺点
- Decorator与它的Component不一样 Decorator是一个透明的包装。如果我们从对象标识的观点出发,一个被装饰了的组件与这个组件是有差别的,因此,使用装饰时不应该依赖对象标识。
- 有许多小对象 采用Decorator模式进行系统设计往往会产生许多看上去类似的小对象。这些对象仅仅在他们相互连接的方式上有所不同,而不是它们的类或是它们的属性值有所不同。尽管对于那些了解这些系统的人来说,很容易对它们进行定制,但是很难学习这些系统,排错也很困难。
5. 适用性
以下情况使用Decorator模式
- 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
- 处理那些可以撤销的职责。
- 当不能采用生成子类的方法进行扩充时。一种情况是,可以有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。
6. 参考资料
- 《大话设计模式》
- 《设计模式——可复用的面向对象软件的基础》