一、装饰器模式是什么?
装饰器模式是一种结构型的软件设计模式,在不改变原类文件或使用继承的前提下,动态地扩展一个对象,进而达到增强或者增加对象功能的目的。
装饰器模式的优点:
- 灵活性好。相比较继承,装饰模式扩展对象功能更加灵活。
- 扩展性好。不同装饰组合,可以创造出各式各样的对象,且避免了类爆炸。
- 满足设计模式要求的开闭原则和合成复用原则。
- 透明性好。客户端针对抽象操作,对具体实现的内容不可见。
装饰器模式的缺点:
- 复杂性高。装饰模式的设计往往具备较高复杂度,对开发者的水平要求高。
二、装饰器模式
2.1 结构图
客户端即Main主函数,装饰器和具体构件有共同接口-抽象构件,因此客户端可以给构件添加多层装饰;另外,装饰器中存放了一个抽象构件的指针,可以区分装饰前和装饰后的状态。
2.2 代码示例
场景描述:我要炒菜,每个菜会有不同的调料添加方案(装饰)。
//Cooking.h /****************************************************/ #pragma once #include <iostream> #include <list> using namespace std; // 抽象构件-烹饪 class Cooking { public: // 构造函数 Cooking(string name = "") :m_name(name) {}; // 析构函数 virtual ~Cooking() {}; // 获取名字 virtual string getName() = 0; protected: string m_name; }; // 具体构件-红烧肉 class SoyBraisedPork :public Cooking { public: // 构造函数 explicit SoyBraisedPork() :Cooking("红烧肉") {}; // 析构函数 virtual ~SoyBraisedPork() {}; // 获取名字 virtual string getName() { return m_name; } };
//Seasoning.h /****************************************************/ #pragma once #include <iostream> #include <list> #include "Cooking.h" using namespace std; // 抽象调料 class Seasoning :public Cooking { public: // 构造函数 Seasoning(Cooking *cooking){ m_cooking = cooking; }; // 析构函数 virtual ~Seasoning() { cout << "开始析构。" << endl; if (m_cooking != nullptr) { cout << "地址:" << m_cooking << endl; delete m_cooking; m_cooking = nullptr; } cout << "结束析构。" << endl; }; protected: Cooking *m_cooking; }; // 具体调料-盐 class Salt :public Seasoning { public: // 构造函数 Salt(Cooking *cooking) :Seasoning(cooking) {}; // 获取名字 virtual string getName() { m_name = m_cooking->getName() + ",加盐"; return m_name; } }; // 具体调料-糖 class Sugar :public Seasoning { public: // 构造函数 Sugar(Cooking *cooking) :Seasoning(cooking) {}; // 获取名字 virtual string getName() { m_name = m_cooking->getName() + ",加糖"; return m_name; } };
//main.cpp /****************************************************/ #include <iostream> #include <string> #include "Seasoning.h" using namespace std; int main() { Cooking *cook = new SoyBraisedPork(); cout << "点菜:" << cook->getName() << endl; cout << "地址:" << cook << endl; cook = new Salt(cook); cout << "点菜:" << cook->getName() << endl; cout << "地址:" << cook << endl; cook = new Sugar(cook); cout << "点菜:" << cook->getName() << endl; cout << "地址:" << cook << endl; delete cook; cook = nullptr; return 0; }
程序结果如下。
有些同学可能会疑惑这种多层装饰的方法,会不会让new的内存没能释放掉。因此在上述示例中,我特地将每次装饰前的地址做了记录,用于复盘整个流程。其实只要在抽象装饰器中,对存放的抽象构件指针进行释放,即可确保没有内存泄漏。
地址000001C529820910是Sugar后cook的地址,客户端中对它析构后,它释放的m_cooking其实和Salt同一内容,也就是000001C52981A3F0,所以析构先把这块释放了,释放它的时候,它里面也有一个m_cooking,这个就是起初红烧肉new出来的地址000001C529819510,因而又出现了开始析构的字样,红烧肉析构完就到底了,这样一套下来,你会发现所有new出来的内容都回收了。
另外,上文介绍的装饰器模式也被成为透明装饰器,也是装饰器标准的样式,客户只操作抽象接口。除此之外,还有一种叫半透明式装饰器,它们的区别在于,半透明装饰器中某些行为是非常规的,这就使得它们无法用同一抽象接口进行迭代,进而无法进行多层装饰,所以在我个人看来,半透明装饰器不具备装饰器模式最特殊的多层装饰属性,文章也就不展开了,大家感兴趣的可以看看其他博主的文章。
三、总结
我尽可能用较通俗的话语和直观的代码例程,来表述我对装饰器模式的理解,或许有考虑不周到的地方,如果你有不同看法欢迎评论区交流!希望我举的例子能帮助你更好地理解装饰器模式。
如果文章帮助到你了,可以点个赞让我知道,我会很快乐~加油!