【设计模式】装饰器模式(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

cpp 复制代码
#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.劣势

存在多个装饰时会变得复杂

(调试难度增加:多层装饰器嵌套时,调用链路较长,排查问题需要逐层追溯。且必须保证装饰器与组件接口一致,否则会破坏模式的统一性)

六、个人理解

装饰器模式是在原有的基础上添加功能,像是为一个产品添加包装盒,一层一层地进行包装,想要继续包装,只需要再套上一层新的包装盒,而想要回到上一个包装,就只要拆开最外层的包装盒。而且这些包装盒(装饰器)不会改变产品(被装饰对象)本身的属性,只是在外面增加了新功能(比如包装的保护、美观作用)------ 就像装饰器不会修改组件的原有逻辑,只是在原有基础上叠加新职责。

但是所存在的问题便是如果我们想要获取中间的某一状态,或者只取出某一个中间包装,就会变得相当麻烦。就像乘法加成之后再去进行加减,就很难再去通过除法进行逆运算,想要完成这种效果,就需要进行相当复杂的数学计算了。

相关推荐
YuanlongWang4 小时前
C# 设计模式——观察者
windows·设计模式·c#
亿牛云爬虫专家5 小时前
中间件实现任务去重与精细化分发:设计模式与常见陷阱
设计模式·中间件·爬虫代理·数据抓取·商品信息·数据去重·电商搜索
消失的旧时光-194316 小时前
kmp需要技能
android·设计模式·kotlin
JohnYan1 天前
安全密钥(Security Key)和认证技术相关词汇表
后端·安全·设计模式
yinghuaqipao1 天前
面向对象——设计模式(创建型)
android·java·设计模式
WaWaJie_Ngen1 天前
【设计模式】代理模式(Proxy)
设计模式·代理模式
麦麦鸡腿堡1 天前
Java的抽象类实践-模板设计模式
java·开发语言·设计模式
WaWaJie_Ngen1 天前
【设计模式】外观模式/门面模式(Facaed)
设计模式·外观模式
Asort1 天前
JavaScript设计模式(十九)——观察者模式 (Observer)
前端·javascript·设计模式