C++装饰器模式及其实现(非常详细)
装饰器模式(Decorator Pattern)用于动态地在对象上添加额外的职责而不修改其原始类的结构。
女士化妆就是一个常见的装饰器模式的例子。可以将女士视为一个对象,将化妆视为一个装饰器。女士本身有一个“原型”,也就是她的基础外貌和特征,但在一些场合下需要让她看起来更年轻、更有活力。这就可以使用化妆这个装饰器来帮助这位女士变得更有魅力。
具体来讲,可以创建一个“化妆”类,这个类接受一个女士对象作为参数,并在女士对象的基础上添加新的功能。例如可以创建一个“眼影”类,这个类接受一个女士对象作为参数,并为她添加涂眼影的功能。类似地,也可以创建“口红”“腮红”等类来为女士添加不同的特色。
通过这样的设计就可以动态地为一名女士添加或修改化妆的功能了。例如可以先给一名女士涂上眼影,然后涂上口红,这样她就会看起来更有精神。也可以随时去掉这些化妆功能,例如擦掉口红或眼影。
这就是装饰器模式的基本思想,也就是在不影响对象自身的基础上动态地添加或修改功能。
在装饰器模式中定义了一个装饰器类,该类将目标类作为其成员变量,并实现与目标对象相同的接口。装饰器类可以在执行目标对象的操作之前或之后添加附加功能。
与继承不同,装饰器模式使用组合的方式,以增加对象的功能和责任。通过多个装饰器对象,可以一层一层地添加新的行为和职责,从而形成一个装饰器链。
装饰器模式可以在不修改现有代码的情况下动态地添加新的功能;通过不同的组合方式,可以实现各种不同的功能组合。通过装饰器模式可以避免复杂的继承层次结构。但是装饰器模式可能会产生过多的细粒度对象,通过多个装饰器对象,可能会创建大量的对象,从而导致系统变得复杂;可能引入冗余的代码,在装饰器链中可能会出现重复的功能代码。

图 1 传统装饰器模式UML简图
ConcreteDecoratorA 和 ConcreteDecoratorB 是具体的装饰对象,这两个模块用于实现具体的装饰的内容,如下图所示。

图 2 C++模板类装饰器模式UML简图
decorateeItfc 是装饰对象接口,对应于 decorator 模板类的模板参数。
decoratee 模板类作为 decorator 的内嵌类用于定义接收 decorateeItfc 模板参数,以便包覆 decorateeItfc 指针模块,方便进行模块管理和后续扩展。
decorator 模板类是装饰器的实现部分,提供了增加装饰、移除装饰、遍历装饰等操作。
提供了灵活的功能扩展接口。通过在 decorator 类中定义一系列操作容器的成员函数和函数对象(如 decratMe() 和 decratMeCallback)以实现对被装饰对象的一系列操作。
使用迭代器支持遍历操作,通过提供 begin() 和 end() 函数,使 decorator 类支持通过迭代器遍历被装饰对象,定义基类接口,代码如下:
宏 BEGIN_DECLARE_DECORTEE_ITFC 用来表示接口声明的开始,宏展开后实际上是一个结构体的定义,类的名字则由宏参数 name 提供,同时使用 name 参数定义同名的虚析构函数来保证内存安全。类定义从 decorator_private__::stDecorateeItfc__ 继承,提供了检查继承和约束关系的能力,代码如下:
宏 DECORTEE_METHOD 是一个可变参的宏,参数 RET 定义了接口返回值;NAME 定义了接口名称,其他可变的参数是接口的参数类型表,并使用 __VA_ARGS__ 在纯虚函数中进行展开。通过 DECORTEE_METHOD 宏可以很方便地规范和定义接口函数。使用时可以使用 DECORTEE_METHOD 多次定义名称或者参数不同的接口。定义完成后使用宏 END_DECLARE_DECORTEE_ITFC() 结束定义,声明接口和结束定义的代码如下:
宏 DECLARE_DECORTEE_DEFAULT 提供了一个简单的接口定义方法,在这个宏的实现中仅仅实现了一个可变参名为 operation() 的函数,虽然函数的名字只有一个,但是可以利用 C++ 的函数重载功能实现多个不同的接口函数,这种方式在功能差异比较大的情况下容易导致代码理解困难,读者应该慎重地在方便性和灵活性之间进行权衡。如果在使用时不需要复杂的接口,则可以使用 DECLARE_DECORTEE_DEFAULT 声名接口,代码如下:
在 decorator 类内部定义了一个嵌套的模板类 decoratee,用于表示被装饰的对象。这样可以保持装饰器类和被装饰对象类的解耦,并且支持对不同类型的对象进行装饰。需要特别注意的是 decoratee 类中对于装饰品的管理并没有拥有并处理对象的内存,使用时需要在自己的代码中对内存进行管理,代码如下:
模块设计了两个增加装饰对象的方法,一个以对象引用的方式添加,另一个以智能指针的方式添加。这两种方法都返回装饰对象在实际存储向量中的索引号。这个索引号将用在移除装饰对象的方法 remove() 中。
在 decorator 类中,并没有对装饰品对象进行内存管理和声明周期管理,在使用时需要在自己的业务代码中进行处理。读者也可以通过修改 decoratee 类将存储对象修改为智能指针以实现自动地对内存进行管理,添加和移除装饰品的代码如下:
模板函数 decratMe() 是一个可变参的模板函数,提供了调用可变参数的能力,但是约束了接口的名称,在 decratMe() 函数中通过 for 循环调用 operation() 函数来执行装饰操作,主要的目的是为宏 DECLARE_DECORTEE_DEFAULT 提供一个执行装饰动作的接口,代码如下:
begin() 和 end() 方法提供了以迭代器访问装饰品的接口,begin() 函数可返回装饰对象向量头部迭代器,如此利用迭代器可以自由地使用装饰对象内容,代码如下:
【实例 1】首先使用宏 DECLARE_DECORTEE_DEFAULT 声明一个名为 IDecoratee 的接口类。DECLARE_DECORTEE_DEFAULT 宏会自动地扩展一个名为 operation() 的纯虚函数。
这个函数的参数类型是 const std::string&,返回值类型是 void,代码如下:
在 main() 函数中首先定义了一个装饰器对象 dcrtr,obj1 和 obj2 分别是两个装饰品,用来装饰 dcrtr 装饰器对象。调用 decratMe() 模板函数的函数执行装饰操作,代码如下:
【实例 2】首先使用接口定义宏定义接口类 ExampleClass__,然后定义一个接口函数 print(),这个函数的返回值是 void,没有需要传入的参数内容,代码如下:
在主函数 main() 中,创建了一个 decorator 对象,以此来装饰 ExampleClass 对象:
这个示例展示了装饰器模式的灵活性和扩展性,可以动态地为对象添加功能,而不必修改其源代码,主程序中的代码如下:
女士化妆就是一个常见的装饰器模式的例子。可以将女士视为一个对象,将化妆视为一个装饰器。女士本身有一个“原型”,也就是她的基础外貌和特征,但在一些场合下需要让她看起来更年轻、更有活力。这就可以使用化妆这个装饰器来帮助这位女士变得更有魅力。
具体来讲,可以创建一个“化妆”类,这个类接受一个女士对象作为参数,并在女士对象的基础上添加新的功能。例如可以创建一个“眼影”类,这个类接受一个女士对象作为参数,并为她添加涂眼影的功能。类似地,也可以创建“口红”“腮红”等类来为女士添加不同的特色。
通过这样的设计就可以动态地为一名女士添加或修改化妆的功能了。例如可以先给一名女士涂上眼影,然后涂上口红,这样她就会看起来更有精神。也可以随时去掉这些化妆功能,例如擦掉口红或眼影。
这就是装饰器模式的基本思想,也就是在不影响对象自身的基础上动态地添加或修改功能。
在装饰器模式中定义了一个装饰器类,该类将目标类作为其成员变量,并实现与目标对象相同的接口。装饰器类可以在执行目标对象的操作之前或之后添加附加功能。
与继承不同,装饰器模式使用组合的方式,以增加对象的功能和责任。通过多个装饰器对象,可以一层一层地添加新的行为和职责,从而形成一个装饰器链。
装饰器模式可以在不修改现有代码的情况下动态地添加新的功能;通过不同的组合方式,可以实现各种不同的功能组合。通过装饰器模式可以避免复杂的继承层次结构。但是装饰器模式可能会产生过多的细粒度对象,通过多个装饰器对象,可能会创建大量的对象,从而导致系统变得复杂;可能引入冗余的代码,在装饰器链中可能会出现重复的功能代码。
传统装饰器模式
装饰器模式的结构包含抽象组件(Component)、具体组件(ConcreteComponent)、装饰器(Decorator)和具体装饰器(ConcreteDecorator),如下图所示:
图 1 传统装饰器模式UML简图
- 抽象组件用于定义抽象接口,可以是抽象类或接口,是被装饰类和装饰器共同实现的接口;
- 具体组件用于实现抽象组件的具体类。它用于定义一个具体的对象,可以为其添加一些功能;
- 装饰器持有一个抽象组件的引用,并实现与抽象组件一致的接口。它在实例化时通过构造函数接收一个抽象组件对象,并在调用相应方法前后添加额外的功能。装饰器可以是抽象类,它通常作为具体装饰器的基类;
- 具体装饰器用于实现装饰器抽象类的具体装饰器类。它在调用父类的方法时,将额外的功能添加到被装饰对象上。
C++11元编程下的结构设计
本文中实现的装饰器模式模块利用 C++ 的模板特性和 STL 容器对对象行为进行动态扩展,能够提高代码的灵活性和可维护性。ConcreteDecoratorA 和 ConcreteDecoratorB 是具体的装饰对象,这两个模块用于实现具体的装饰的内容,如下图所示。

图 2 C++模板类装饰器模式UML简图
decorateeItfc 是装饰对象接口,对应于 decorator 模板类的模板参数。
decoratee 模板类作为 decorator 的内嵌类用于定义接收 decorateeItfc 模板参数,以便包覆 decorateeItfc 指针模块,方便进行模块管理和后续扩展。
decorator 模板类是装饰器的实现部分,提供了增加装饰、移除装饰、遍历装饰等操作。
C++装饰器模式实现和解析
本模块通过模板类 decorator 使用模板参数 itfcType 来指定需要被装饰的接口类型,实现了对不同类型对象的装饰功能,提供了两个宏以方便和规范实现接口的定义方法,并利用一个不暴露的类定义来约束接口的继承关系。提供了灵活的功能扩展接口。通过在 decorator 类中定义一系列操作容器的成员函数和函数对象(如 decratMe() 和 decratMeCallback)以实现对被装饰对象的一系列操作。
使用迭代器支持遍历操作,通过提供 begin() 和 end() 函数,使 decorator 类支持通过迭代器遍历被装饰对象,定义基类接口,代码如下:
//designM/decorator.hpp namespace decorator_private__ { struct stDecorateeItfc__{ virtual~stDecorateeItfc__(){} }; }
宏 BEGIN_DECLARE_DECORTEE_ITFC 用来表示接口声明的开始,宏展开后实际上是一个结构体的定义,类的名字则由宏参数 name 提供,同时使用 name 参数定义同名的虚析构函数来保证内存安全。类定义从 decorator_private__::stDecorateeItfc__ 继承,提供了检查继承和约束关系的能力,代码如下:
//designM/decorator.hpp #define BEGIN_DECLARE_DECORTEE_ITFC(name) \ struct name:public decorator_private__::stDecorateeItfc__{ \ virtual~name(){}
宏 DECORTEE_METHOD 是一个可变参的宏,参数 RET 定义了接口返回值;NAME 定义了接口名称,其他可变的参数是接口的参数类型表,并使用 __VA_ARGS__ 在纯虚函数中进行展开。通过 DECORTEE_METHOD 宏可以很方便地规范和定义接口函数。使用时可以使用 DECORTEE_METHOD 多次定义名称或者参数不同的接口。定义完成后使用宏 END_DECLARE_DECORTEE_ITFC() 结束定义,声明接口和结束定义的代码如下:
//designM/decorator.hpp #define DECORTEE_METHOD(RET,NAME,...) \ virtual RET NAME(__VA_ARGS__) = 0; #define END_DECLARE_DECORTEE_ITFC() };
宏 DECLARE_DECORTEE_DEFAULT 提供了一个简单的接口定义方法,在这个宏的实现中仅仅实现了一个可变参名为 operation() 的函数,虽然函数的名字只有一个,但是可以利用 C++ 的函数重载功能实现多个不同的接口函数,这种方式在功能差异比较大的情况下容易导致代码理解困难,读者应该慎重地在方便性和灵活性之间进行权衡。如果在使用时不需要复杂的接口,则可以使用 DECLARE_DECORTEE_DEFAULT 声名接口,代码如下:
//designM/decorator.hpp #define DECLARE_DECORTEE_DEFAULT(name,...) \ BEGIN_DECLARE_DECORTEE_ITFC(name) \ DECORTEE_METHOD(void,operation,__VA_ARGS__) \ END_DECLARE_DECORTEE_ITFC()
在 decorator 类内部定义了一个嵌套的模板类 decoratee,用于表示被装饰的对象。这样可以保持装饰器类和被装饰对象类的解耦,并且支持对不同类型的对象进行装饰。需要特别注意的是 decoratee 类中对于装饰品的管理并没有拥有并处理对象的内存,使用时需要在自己的代码中对内存进行管理,代码如下:
//designM/decorator.hpp class decoratee { protected: itfc_t * p_imp__; //实际的装饰品,类似实际的化妆品 public: decoratee():p_imp__(nullptr) {} decoratee(itfc_t * imp):p_imp__(imp){} decoratee(decoratee&& b): p_imp__(b.p_imp__) {} decoratee& operator=(decoratee&& b) { p_imp__ = b.p_imp__; return *this; } //方便调用实际的对象 itfc_t * operator->() { return p_imp__; } void set(itfc_t * imp) { p_imp__ = imp; } itfc_t * get() { return p_imp__; } //装饰品数据类型包装数据类型 using drte_t = decoratee; //装饰品 using data_t = std::vector< drte_t >; using iterator = typename data_t::iterator; protected: data_t m_drtes__; public: decorator() {} virtual ~decorator() {}
模块设计了两个增加装饰对象的方法,一个以对象引用的方式添加,另一个以智能指针的方式添加。这两种方法都返回装饰对象在实际存储向量中的索引号。这个索引号将用在移除装饰对象的方法 remove() 中。
在 decorator 类中,并没有对装饰品对象进行内存管理和声明周期管理,在使用时需要在自己的业务代码中进行处理。读者也可以通过修改 decoratee 类将存储对象修改为智能指针以实现自动地对内存进行管理,添加和移除装饰品的代码如下:
//designM/decorator.hpp size_t decrat(dcrte_t&dcrtee) { m_dcrtes__.push_back(dcrtee); return m_dcrtes__.size() - 1; } size_t decrat(itfcType * dcrtee) { m_dcrtes__.push_back(dcrte_t(dcrtee)); return m_dcrtes__.size() - 1; } void remove(size_t idx) { if(idx< m_dcrtes__.size()){ m_dcrtes__.erase(m_dcrtes__.begin() + idx); } }
模板函数 decratMe() 是一个可变参的模板函数,提供了调用可变参数的能力,但是约束了接口的名称,在 decratMe() 函数中通过 for 循环调用 operation() 函数来执行装饰操作,主要的目的是为宏 DECLARE_DECORTEE_DEFAULT 提供一个执行装饰动作的接口,代码如下:
template< typename...Params > void decratMe(Params&&...args) { for(size_t i = 0;i< m_dcrtes__.size(); ++i){ m_dcrtes__[i]->operation(std::forward<Params >(args)...); } }函数 decratMeCallback() 有两个不同的实现,它们都是以回调函数的方式调用装饰动作的接口,调用这个函数将对所有的装饰对象进行遍历处理,将每个装饰品以传参的方式提交函数对象,以便在函数对象中进行使用。使用函数的对象的主要目的是提高装饰操作时的灵活性,将可变的内容留在实际工程代码中实现,代码如下:
//designM/decorator.hpp void decratMeCallback(std::function< void(dcrte_t&) > cb) { size_t count = m_dcrtes__.size(); for(size_t i = 0;i< count;i ++){ cb(m_dcrtes__[i]); } } void decratMeCallback(std::function< void(itfc_t * itfc) > cb) { size_t count = m_dcrtes__.size(); for(size_t i = 0;i< count;i ++){ cb(m_dcrtes__[i].get()); } }
begin() 和 end() 方法提供了以迭代器访问装饰品的接口,begin() 函数可返回装饰对象向量头部迭代器,如此利用迭代器可以自由地使用装饰对象内容,代码如下:
iterator begin(){return m_dcrtes__.begin();} iterator end(){return m_dcrtes__.end();} };
C++装饰器模式应用示例
下面给出了两个使用示例:- 第 1 个是采用简单接口方式实现的示例。使用已经约定的接口 operation() 方法,并通过 operation() 实现具体的操作;
- 第 2 种方式使用了宏声明更加复杂的接口方式,并通过回调函数的方式来处理具体操作。
【实例 1】首先使用宏 DECLARE_DECORTEE_DEFAULT 声明一个名为 IDecoratee 的接口类。DECLARE_DECORTEE_DEFAULT 宏会自动地扩展一个名为 operation() 的纯虚函数。
这个函数的参数类型是 const std::string&,返回值类型是 void,代码如下:
DECLARE_DECORTEE_DEFAULT(IDecoratee,const std::string&)接下来实现被装饰者接口的具体类 ConcreteDecoratee1 和 ConcreteDecoratee2。这两个具体装饰品类都从 IDecoratee 继承而来,并实现了 operation() 函数,以此实现具体操作。在本例中通过终端输出自己类型的名字和传入的参数内容,代码如下:
//decorator.cpp class ConcreteDecoratee1:public IDecoratee{ public: void operation(const std::string&str)override{ std::cout<< "ConcreteDecoratee 1:"<< str<< std::endl; } }; class ConcreteDecoratee2:public IDecoratee{ public: void operation(const std::string&str)override{ std::cout<< "ConcreteDecoratee 2:"<< str<< std::endl; } };
在 main() 函数中首先定义了一个装饰器对象 dcrtr,obj1 和 obj2 分别是两个装饰品,用来装饰 dcrtr 装饰器对象。调用 decratMe() 模板函数的函数执行装饰操作,代码如下:
//decorator.cpp int main() { decorator<IDecoratee > dcrtr; ConcreteDecoratee1 * obj1 = new ConcreteDecoratee1(); ConcreteDecoratee2 * obj2 = new ConcreteDecoratee2(); //将被装饰者对象添加到装饰者 size_t idx1 = dcrtr.decrat(obj1); size_t idx2 = dcrtr.decrat(obj2); //对所有被装饰者对象进行操作 dcrtr.decratMe("Hello"); delete obj1; delete obj2; return 0; }上面的例子的执行结果如下:
ConcreteDecoratee 1:Hello
ConcreteDecoratee 2:Hello
【实例 2】首先使用接口定义宏定义接口类 ExampleClass__,然后定义一个接口函数 print(),这个函数的返回值是 void,没有需要传入的参数内容,代码如下:
//decorator2.cpp BEGIN_DECLARE_DECORTEE_ITFC(ExampleClass__) DECORTEE_METHOD(void,print) END_DECLARE_DECORTEE_ITFC() class ExampleClass:public ExampleClass__ { public: std::string name; ExampleClass(){} ExampleClass(const std::string&n):name(n){} void print() { std::cout<< "Name:"<< name<< std::endl; } };
在主函数 main() 中,创建了一个 decorator 对象,以此来装饰 ExampleClass 对象:
- 首先创建了名为 Obj1 和 Obj2 的 ExampleClass 对象,并通过 decorator 对象进行装饰;
- 然后使用 Lambda 表达式对装饰的 ExampleClass 对象进行打印操作;
- 接着移除了第 1 个装饰的对象,并再次使用 Lambda 表达式对剩余的 ExampleClass 对象进行打印;
- 最后释放了内存,并返回 0,表示成功。
这个示例展示了装饰器模式的灵活性和扩展性,可以动态地为对象添加功能,而不必修改其源代码,主程序中的代码如下:
//decorator2.cpp #include <iostream> #include "designM/decorator.hpp" int main() { decorator<ExampleClass> dec; //创建一个 ExampleClass 对象并装饰 ExampleClass * obj1 = new ExampleClass("Obj1"); dec.decorat(obj1); //创建一个 ExampleClass 对象并装饰 ExampleClass * obj2 = new ExampleClass("Obj2"); dec.decorat(obj2); //使用 Lambda 表达式打印所有装饰的 ExampleClass 对象 dec.decoratMeCallback([](decorator<ExampleClass>::drte_t& obj) { obj->print(); }); //删除第 1 个装饰的对象 dec.remove(0); std::cout << "After remove:" << std::endl; //再次使用 Lambda 表达式打印所有装饰的 ExampleClass 对象 dec.decoratMeCallback([](decorator<ExampleClass>::drte_t& obj) { obj->print(); }); delete obj1; delete obj2; return 0; }上面的例子的执行结果如下:
Name:Obj1
Name:Obj2
After remove:
Name:Obj2