本篇文章,来解读《大话设计模式》的第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++编程,来演示装饰模式的使用。