六、装饰模式
装饰(Decorator) 模式也称为装饰器模式/包装模式,是一种结构型模式。这是一个非常有趣和值得学习的设计模式,该模式展现出了运行时的一种扩展能力,以及比继承更强大和灵活的设计视角和设计能力,甚至在有些场合下,不使用该模式很难解决问题。
在本模式的讲解过程中,会提及类与类之间的继承关系和组合关系,还会引出面向对象程序设计的一个重要原则——组合复用原则。
6.1 问题的提出
继续前面的闯关打斗类游戏。在游戏中,不但会出现各种人物、怪物,还会出现许多UI (用户接口)界面。例如,主角一般会随身携带背包,背包中的每个格子用于放置一个物品,根据策划的规定,背包中格子数量比较多时,还会在背包右侧显示出滚动条,如图6.1所示。
再例如,有时需要显示一些公告信息,如图6.2所示。当公告信息太长时,也可能会出现滚动条等。
图6.1和图6.2这些看得见的UI界面元素称为控件。例如,要向其中输入文字可以使用文本控件,要显示多行信息可以使用列表控件等,当要显示的内容过长或者过宽时会显示出滚动条控件。
这里就以一个最简单的控件——列表控件为例,说明如何丰富该控件上所显示的内容,图6.3中第1幅子图是一个普通的列表控件,第2幅图增加了边框让其更有立体感,第3幅图增加了一个垂直滚动条,而第4幅图又增加了一个水平滚动条。
传统继承方案的问题
- 子类膨胀:新增功能(如阴影、外发光)需不断创建子类。
- 灵活性差:无法灵活组合功能(如无框但有垂直滚动条的控件)。
解决方案思路
采用组装方式动态添加功能:
- 基础控件(
ListCtrl
)作为核心。 - 附加功能(边框、滚动条)作为装饰器,通过组合方式叠加。
6.2 引入装饰模式
组合复用原则(CRP)
核心思想:优先使用组合而非继承,以降低类间耦合,避免父类代码冗余。
装饰模式实现代码
1. 抽象构件(Control)
// 抽象的控件类
class Control {
public:virtual void draw() = 0;
public:virtual ~Control() {}
};
2. 具体构件(ListCtrl)
// 列表控件类
class ListCtrl : public Control {
public:virtual void draw() {cout << " 绘制普通的列表控件!" << endl;}
};
3. 抽象装饰器(Decorator)
// 抽象的装饰器类
class Decorator : public Control {
public:Decorator(Control* tmpctrl) : m_control(tmpctrl) {} // 组合关系virtual void draw() {m_control->draw(); // 委托给被装饰对象}
private:Control* m_control;
};
4. 具体装饰器
边框装饰器
class BorderDec : public Decorator {
public:BorderDec(Control* tmpctrl) : Decorator(tmpctrl) {}virtual void draw() {Decorator::draw(); // 先绘制原内容drawBorder(); // 再绘制新增内容}
private:void drawBorder() { cout << " 绘制边框!" << endl; }
};
垂直滚动条装饰器
class VerScrollBarDec : public Decorator {
public:VerScrollBarDec(Control* tmpctrl) : Decorator(tmpctrl) {}virtual void draw() {Decorator::draw();drawVerScrollBar();}
private:void drawVerScrollBar() { cout << " 绘制垂直滚动条!" << endl; }
};
客户端代码示例
int main() {// 组装带边框和垂直滚动条的控件Control* base = new ListCtrl();Control* withBorder = new BorderDec(base);Control* final = new VerScrollBarDec(withBorder);final->draw(); // 输出:普通列表 → 边框 → 垂直滚动条cout << "------------------------" << endl;// 组装只带水平滚动条的控件Control* base2 = new ListCtrl();Control* withHor = new HorScrollBarDec(base2);withHor->draw(); // 输出:普通列表 → 水平滚动条// 释放资源(注意顺序)delete final;delete withBorder;delete base;delete withHor;delete base2;return 0;
}
装饰模式UML图
模式角色
角色 | 说明 | 示例类 |
---|---|---|
抽象构件 | 定义统一接口 | Control |
具体构件 | 基础功能实现 | ListCtrl |
抽象装饰器 | 持有构件引用,定义装饰接口 | Decorator |
具体装饰器 | 实现具体装饰逻辑 | BorderDec 等 |
6.3 饮料价格计算范例
问题描述
- 基础饮料:10元
- 可选配料:砂糖(+1元)、牛奶(+2元)、珍珠(+2元)
实现代码
1. 抽象构件(Beverage)
class Beverage {
public:virtual int getPrice() = 0;virtual ~Beverage() {}
};
2. 具体构件(FruitBeverage)
class FruitBeverage : public Beverage {
public:int getPrice() override { return 10; }
};
3. 抽象装饰器(CondimentDecorator)
class CondimentDecorator : public Beverage {
protected:Beverage* beverage;
public:CondimentDecorator(Beverage* b) : beverage(b) {}
};
4. 具体装饰器
砂糖装饰器
class Sugar : public CondimentDecorator {
public:Sugar(Beverage* b) : CondimentDecorator(b) {}int getPrice() override { return beverage->getPrice() + 1; }
};
珍珠装饰器
class Bubble : public CondimentDecorator {
public:Bubble(Beverage* b) : CondimentDecorator(b) {}int getPrice() override { return beverage->getPrice() + 2; }
};
客户端代码
int main() {Beverage* drink = new FruitBeverage();drink = new Bubble(drink); // 加珍珠(+2)drink = new Sugar(drink); // 加砂糖(+1)cout << "总价格:" << drink->getPrice() << "元" << endl; // 输出:13元delete drink;return 0;
}
饮料范例UML图
总结:装饰模式通过组合实现动态功能扩展,避免继承导致的类爆炸,符合开闭原则,适用于需要灵活添加可选功能的场景。