【设计模式】装饰器模式(Decorator)
目录
一、问题导入
二、问题剖析
三、结构成分
四、代码实现(仅供参考)
五、优劣
1.优势
2.劣势
六、个人理解
前言:老师的课件只有意图、类图、例图和优劣,在一些细节上缺少过渡,此外,老师的课堂内容并无法准确的体现为什么要去使用装饰器模式(比如为什么我们不直接使用继承的方式或者函数去进行实现)。所以我会进行适当的扩充。(其实就是个人理解发挥比较多,可能存在不恰当的地方,希望大家能及时指出,我会尽快修改的)
一、问题导入
在游戏《箭箭箭》中,玩家每通过一个通道,属性(如箭矢数量、攻击速度)就会发生一次改变。此时若将所有属性都封装在玩家类内部,通过调用类自身函数修改属性值,确实能实现单向的属性叠加。但这种方式存在局限 —— 如果后续要新增 “箭矢穿透”“暴击概率” 等新属性,就必须修改玩家类的源码,违背了 “对扩展开放、对修改关闭” 的设计原则。而如果用继承实现,会导致类爆炸(每增加一种属性组合就需要一个新类),且无法动态叠加 / 移除属性

而在《皇城突袭》里,场景需求更复杂。玩家不仅需要给防御塔或英雄动态叠加技能加成(如 “攻击力 + 20%”“攻击范围扩大”),还可能因误操作需要回退 —— 清除所有加成,让状态回归初始。这就要求方案同时满足两个核心诉求:一是能不修改原有系统就动态添加新能力,二是能灵活拆解已添加的能力实现回退。
恰好,装饰器模式就是为解决这类 “动态扩展 + 灵活拆解” 问题而生的经典设计方案。它通过 “对象组合” 而非 “类继承” 或 “内部修改” 的方式,既能轻松叠加新职责,又能追溯回初始状态 —— 我们接下来就具体探讨它的实现逻辑。

二、问题剖析
接下来我用一个更为简单的例子去进行解析。
在《飞机大战》中,飞机需要通过局内加成实时更改速度属性。我们可以用装饰器模式实现这一需求:
首先定义一个 Plane 基类(或接口),规定所有飞机都必须实现的核心方法(如getSpeed()获取当前速度);然后创建 BasicPlane 类,作为具体的基础飞机,实现基类的方法(比如初始速度 100)。
接下来设计装饰器抽象类 PlaneDecorator:它需要继承 Plane 基类(保证与所有飞机有相同的接口,让调用者无需区分是基础飞机还是被装饰的飞机),同时持有一个 Plane 对象(这个对象可以是基础飞机,也可以是被其他装饰器包裹过的飞机,用于保留原始状态)。
最后实现具体的装饰器(如 SpeedUpDecorator、AttackSpeedDecorator 等):每个装饰器在重写getSpeed()等方法时,会先调用被持有 Plane 对象的原始方法(比如基础飞机的 100 速度),再叠加自身的加成(比如 + 50),最终返回 150。
通过这种 “用装饰器包裹原始对象,每次叠加都基于上一层状态” 的方式,我们既能动态给飞机添加各种属性加成,又能随时移除某一层装饰器(比如去掉 SpeedUpDecorator),让飞机状态回退到上一层 —— 这正是装饰器模式的灵活之处。

三、结构成分
从问题剖析当中,我们可以抽象出其装饰器模式的组成:
(1)组件:定义所有具体组件和装饰器的统一接口,保证装饰器与被装饰对象 “对外表现一致”(Plane)
(2)具体组件:实现组件接口的基础功能,是装饰器的 “起点”(被装饰的原始对象)(BasicPlane)
(3)装饰器:作为所有具体装饰器的基类,通过持有组件对象实现 “对原始功能的复用”,同时继承组件接口保证 “接口一致性”(PlaneDecorator)
(4)具体装饰器:在装饰器基类的基础上,添加具体的额外功能(如速度加成),并在调用时先复用被装饰对象的功能(SpeedDecorator)
四、代码实现(仅供参考)
由于装饰器持有上一层对象的引用,回退时只需丢弃当前装饰器,直接使用被持有的对象即可(例如SpeedDecorator持有BasicPlane,移除时直接用BasicPlane)
#pragma once
#include<iostream>namespace _DecoratorPattern
{//组件(飞机)class Plane{public:virtual int get_speed() = 0;};//具体组件class BasicPlane:public Plane{public:int get_speed() override { return 100; }};//装饰器class PlaneDecorator:public Plane{public:PlaneDecorator(Plane* plane) { this->plane = plane; }int get_speed() override { return plane->get_speed(); }private:Plane* plane;};//具体装饰器class SpeedDecorator:public PlaneDecorator{public:SpeedDecorator(Plane* plane) :PlaneDecorator(plane) {}int get_speed() override { return PlaneDecorator::get_speed() + 50; }};void test(){BasicPlane* plane = new BasicPlane();SpeedDecorator* decorator = new SpeedDecorator(plane);std::cout << decorator->get_speed() << std::endl;decorator = new SpeedDecorator(decorator);std::cout << decorator->get_speed() << std::endl;//释放内存delete plane;delete decorator;}
}
五、优劣
1.优势
(1)装饰类和被装饰类可独立开发,互不耦合
(2)可动态扩展功能
(可以在运行时选择不同的装饰组合,实现灵活的功能搭配。且避免了使用继承时的类层级膨胀问题)
2.劣势
存在多个装饰时会变得复杂
(调试难度增加:多层装饰器嵌套时,调用链路较长,排查问题需要逐层追溯。且必须保证装饰器与组件接口一致,否则会破坏模式的统一性)
六、个人理解
装饰器模式是在原有的基础上添加功能,像是为一个产品添加包装盒,一层一层地进行包装,想要继续包装,只需要再套上一层新的包装盒,而想要回到上一个包装,就只要拆开最外层的包装盒。而且这些包装盒(装饰器)不会改变产品(被装饰对象)本身的属性,只是在外面增加了新功能(比如包装的保护、美观作用)—— 就像装饰器不会修改组件的原有逻辑,只是在原有基础上叠加新职责。
但是所存在的问题便是如果我们想要获取中间的某一状态,或者只取出某一个中间包装,就会变得相当麻烦。就像乘法加成之后再去进行加减,就很难再去通过除法进行逆运算,想要完成这种效果,就需要进行相当复杂的数学计算了。
