本篇文章,来解读《大话设计模式》的第6章——装饰模式。并通过C++代码实现实例代码的功能。
注:第3~6章讲的是设计模式中的一些原则(第3章:单一职责原则;第4章:开放-封闭原则;第5章:依赖倒转原则和里氏替换原则),这些原则在设计模式中用到时会提及,暂不做专门解读。
1 装饰器模式
装饰模式,或称装饰器模式(Decorator),动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更加灵活
我们在给对象增加功能时,一种做法是再设计一个子类来继续父类,然后给子类添加额外的功能,另一种做法就是通过装饰模式,设计单独用于装饰功能的类,通过“包装”的形式动态给某个对象增加功能。
2 穿搭衣服实例
题目:用控制台程序,写可以给人搭配衣服的代码
2.1 版本一
版本一的代码,仅定义了一个Person类,提供6种不同服饰的装扮接口,以及1个名字展示接口。
2.1.1 Person类
Person类的代码如下,维护一个人的名字,然后是6种服饰的穿衣接口,就是加一句打印,最后Show接口显示人的名字。
class Person
{
public:Person(std::string name){m_name = name;}void WearTShirts(){printf("大T恤 ");}void WearBigTrouser(){printf("垮裤 ");}void WearSneakrs(){printf("破球鞋 ");}void WearSuit(){printf("西装 ");}void WearTie(){printf("领带 ");}void WearLeatherShoes(){printf("皮鞋 ");}void Show(){printf("装扮的%s", m_name.c_str());} private:std::string m_name;
};
2.1.2 主函数
主函数的逻辑如下,先实例化一个名为"小菜"的Person对象,然后依次调用穿衣接口,最后调用展示接口:
#include <iostream>int main()
{Person xc = Person("小菜");printf("\n第一种装扮:");xc.WearTShirts();xc.WearBigTrouser();xc.WearSneakrs();xc.Show();printf("\n第二种装扮:");xc.WearSuit();xc.WearTie();xc.WearLeatherShoes();xc.Show(); printf("\n"); return 0;
}
代码运行效果如下:
版本一这种方式,虽然功能实现了,但如果想要增加装扮,就需要修改Person类了,这违反了面向对象设计中的开放-封闭原则。
开放-封闭原则:是指软件实体(类、模块、函数等等)应该可以扩展,但是不可修改。
换句话说:
- 开放:对扩展开放,当需要增加新功能时,通过代码扩展的方式(如增加新的类)实现
- 封闭:对修改封闭,当需要增加新功能时,尽量避免对原有代码的修改
下面来看版本二如何实现。
2.2 版本二
版本二是将各种服饰单独封装了起来,并继承自服饰抽象类,将服饰类与人类进行了分离,类图如下:
这样,后续需要增加服饰时,只需要增加对应的具体服饰类,而不会影响其它已有的服饰类的代码。
2.2.1 Person类与服饰类
Person类与服饰类的代码如下,Person类只维护一个人的名字,并通过Show接口显示人的名字。服饰类的Show接口用于显示服饰的名称,具体显示的内容由具体服饰类的Show接口实现,也是打印出服饰的名字。
// Person类
class Person
{
public:Person(std::string name){m_name = name;}void Show(){printf("装扮的%s", m_name.c_str());} private:std::string m_name;
};// 服饰类
class Finery
{
public:virtual void Show(){};
};// 各种服饰子类
class TShirts : public Finery
{
public:void Show(){printf("大T恤 ");}
};class BigTrouser : public Finery
{
public:void Show(){printf("垮裤 ");}
};class Sneakrs : public Finery
{
public:void Show(){printf("破球鞋 ");}
};class Suit : public Finery
{
public:void Show(){printf("西装 ");}
};class Tie : public Finery
{
public:void Show(){printf("领带 ");}
};class LeatherShoes : public Finery
{
public:void Show(){printf("皮鞋 ");}
};
2.2.2 主函数
主函数的逻辑如下,先实例化一个名为"小菜"的Person对象,
然后依次实例化具体要装扮的服饰类并调用对应的展示接口,
最后调用Person的展示接口:
int main()
{Person xc = Person("小菜"); //先实例化一个名为"小菜"的Person对象printf("\n第一种装扮:");TShirts dtx; //依次实例化具体要装扮的服饰类BigTrouser kk;Sneakrs pqx;dtx.Show(); //调用对应的展示接口kk.Show();pqx.Show();xc.Show(); //最后调用Person的展示接口printf("\n第二种装扮:");Suit xz;Tie ld;LeatherShoes px;xz.Show();ld.Show();px.Show();xc.Show(); printf("\n"); return 0;
}
代码运行效果如下:
版本二中,Rerson类和服饰类是完全独立的,搭配衣服的过程也只是一个一个将对应词打印出来。下面来看版本三。
2.3 版本三
版本三用到了本篇要讲的装饰器模式。
在本例中,服饰就是装饰类,具体的服饰,T恤、球鞋这些是具体的装饰类,而人就是要被装饰的组件,那是组件Component还是具体组件ConcreateComponet呢?
本例中,只有一个ConcreateComponet具体组件类,就没必要单独建立一个Component组件类,可以把两者职责合并成一个类,也就是只使用ConcreateComponet类表示Person类。
2.3.1 Person类与服饰类
Person类与服饰类的代码如下:
// Person类
class Person
{
public:Person(){};Person(std::string name){m_name = name;}// 父类的函数virtual void Show(){printf("装扮的%s", m_name.c_str());} private:std::string m_name;
};// 服饰类(Decorator)
class Finery : public Person
{
protected:Person *m_pComponent = nullptr;public:void Decorate(Person *pComponent){m_pComponent = pComponent;}// 子类的函数void Show(){if (m_pComponent != nullptr){m_pComponent->Show();}}
};// 具体服饰类(ConcreateDecorator)
class TShirts : public Finery
{
public:void Show(){printf("大T恤 ");Finery::Show();}
};class BigTrouser : public Finery
{
public:void Show(){printf("垮裤 ");Finery::Show();}
};class Sneakrs : public Finery
{
public:void Show(){printf("破球鞋 ");Finery::Show();}
};class Suit : public Finery
{
public:void Show(){printf("西装 ");Finery::Show();}
};class Tie : public Finery
{
public:void Show(){printf("领带 ");Finery::Show();}
};class LeatherShoes : public Finery
{
public:void Show(){printf("皮鞋 ");Finery::Show();}
};
2.3.2 主函数
主函数的逻辑如下,先实例化一个名为"小菜"的Person对象,
然后再依次实例化要装扮的服饰类对象,接着通过“包装”的方式,后一个对象对前一个对象进行包装,
最后调用最终包装后对象的展示接口:
int main()
{Person *xc = new Person("小菜");printf("\n第一种装扮:");TShirts *dtx = new TShirts();BigTrouser *kk = new BigTrouser(); Sneakrs *pqx = new Sneakrs();dtx->Decorate(xc); // 装饰过程:先用大裤衩装饰小菜kk->Decorate(dtx); // 再用垮裤装饰穿了大裤衩的小菜pqx->Decorate(kk); // 再用破球鞋装饰穿了大裤衩和垮裤的小菜pqx->Show(); // 最后调用最外层的装饰对象的展示接口printf("\n第二种装扮:");Suit *xz = new Suit();Tie *ld = new Tie();LeatherShoes *px = new LeatherShoes();xz->Decorate(xc); // 装饰过程:先用西装装饰小菜ld->Decorate(xz); // 再用领带装饰穿了西装的小菜px->Decorate(ld); // 再皮鞋装饰穿了西装和领带的小菜px->Show(); // 最后调用最外层的装饰对象的展示接口 printf("\n"); return 0;
}
代码运行效果如下:
装饰模式的优缺点与适用场景:
3 总结
本篇介绍了设计模式中的装饰模式,并通过给人装扮的实例,使用C++编程,来演示装饰模式的使用。