0%

【设计模式】装饰者模式

本篇介绍了装饰者模式及相关的面向对象设计原则。装饰者模式动态的将责任附加到对象身上,扩展功能时,装饰者模式相比于继承能为系统带来更好的弹性。

1 引例

一个咖啡饮料系统需要为所有类型的饮料定义各自的类,最原始的设计是这样:

image-20230302164118582

在购买饮料时,顾客可以在某种饮料的基础上加入各种调料,比如奶泡、摩卡等,不同调料的价格也不同,组合而成的饮料价格也就不同,不同的调料搭配不同的饮料可能会出现无数种组合,如果一味的使用继承,将会使饮料子类的数量急剧膨胀,难以管理的同时还会出现大量的重复代码。因此我们希望能有一种设计模式来解决这种问题。

2 装饰者模式

设计原则:开放-关闭原则,对扩展开放,对修改关闭。

开放-关闭原则是指当一个类设计好之后,尽可能不对他进行修改,但又能允许该类进行扩展,也就是在不修改现有代码的前提下,扩展新的行为。装饰者模式就实现了这一目的。

以一杯加了奶泡和摩卡的深焙咖啡为例,构建这样一种饮品的流程是:

image-20230302165122699

image-20230302165135782

image-20230302165152185

这就是一个装饰者模式的流程,装饰者模式的类图结构如下:

image-20230302165414471

按着上面的类图,饮料就作为组件,具体的饮料类型对应于具体组件,而调料作为装饰器,具体的调料对应不同的具体装饰器,于是我们可以开始用装饰者模式实现上面的饮料系统。

首先是组件(饮料)类:

1
2
3
4
5
6
7
8
9
10
11
class Component {
public:
// 其他内容...

string discription = "Unknow Beverage";
string getDiscription() {
return discription;
}

virtual double cost() = 0;
};

装饰器(调料)要和组件是同一类,这样才能保证所有装饰器都能代替组件,调用同一个接口,因此需要继承于组件类:

1
2
3
4
5
class Decorator : public Component {
public:
// 所有调料都要有自己的描述,这样才能输出完整的配料
virtual string getDiscription() = 0;
};

接下来实现一个具体组件类,比如浓缩咖啡(Espresso)类:

1
2
3
4
5
6
7
8
9
10
class Espresso : public Component {
public:
Espresso() {
discription = "Espresso";
}

virtual double cost() {
return 1.99;
}
};

然后实现一个具体装饰器,比如摩卡(Mocha)类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Mocha : public Decorator {
private:
Component* beverage; //被装饰者
public:
Mocha(Component* b) : beverage(b) {}

virtual string getDiscription() {
return beverage->getDiscription() + ", Mocha";
}

virtual double cost() {
return beverage->cost() + 0.2;
}
};

按照上面的方法可以构建不同的饮料和调料,之后就可以随意组合了,在调用的时候也会变得无比方便:

1
2
3
4
5
6
7
8
9
int main() {
Component* beverage = new Espresso(); // 一杯浓缩咖啡
beverage = new Mocha(beverage); // 加入一份摩卡
beverage = new Whip(beverage); // 加入一份奶泡
beverage = new Sugar(beverage); // 加入一份糖
cout << beverage.getDiscription() << endl;
cout << beverage.cost() << endl;
return 0;
}

总结来看,装饰者类是一个组件类,也就是和被装饰者是 is-a 的关系,但同时装饰者类中又包含组件类,对组件类进行扩展,因此二者之间还存在 has-a 的关系。装饰者模式利用组合解决了继承带来的子类膨胀和灵活性差的问题。

---- 本文结束 知识又增加了亿点点!----

文章版权声明 1、博客名称:LycTechStack
2、博客网址:https://lz328.github.io/LycTechStack.github.io/
3、本博客的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,请联系博主进行删除处理。
4、本博客所有文章版权归博主所有,如需转载请标明出处。