0x07.装饰器模式
装饰器模式(Decorator Pattern)。它解决的是“如何动态地给对象添加新功能,同时避免产生无数个子类”的问题。
在游戏开发中,这是处理“系统机制/Buff叠加”的最强王牌!
💡 为什么要用装饰器模式?
假设你正在做一款像《哈迪斯》(Hades)或《死亡细胞》(Dead Cells)那样的肉鸽(Roguelike)游戏。玩家的基础武器是一把普通的剑,但在游戏过程中可以获得各种强化(附魔):
- 火焰附魔(增加燃烧伤害)
- 冰霜附魔(增加减速效果)
- 剧毒附魔(增加持续毒伤)
新手的灾难写法(继承深渊):
一开始你可能想用类的继承来解决:写一个 Sword 类,然后派生出 FireSword,IceSword。
但如果玩家同时获得了“火焰”和“冰霜”呢?你只能写一个 FireIceSword 类。如果再加上“剧毒”和“吸血”呢?各种组合加在一起,你会遭遇可怕的“类爆炸”,需要写几十甚至上百个子类!
装饰器模式的优雅解法:
不要通过继承来扩展功能,而是像包洋葱一样,把对象一层一层地“包装(装饰)”起来。无论你包了多少层,最外层的表现依然是一把武器。
一、 标准的 C# 装饰器写法
这是一种纯面向对象的数据处理方式,非常适合用来计算叠加后的伤害值或生成长串的属性描述。
1. 定义核心组件接口(定义什么是“武器”)
// 所有武器和附魔都必须实现这个接口
public interface IWeapon
{
float GetDamage();
string GetDescription();
}
2. 实现基础组件(那把毫无修饰的新手剑)
// 基础武器:长剑
public class BasicSword : IWeapon
{
public float GetDamage()
{
return 10f; // 基础伤害 10
}
public string GetDescription()
{
return "普通长剑";
}
}
3. 定义装饰器基类(核心灵魂)
这个类既是一个 IWeapon,它的肚子里又装着一个 IWeapon。
// 装饰器基类
public abstract class WeaponDecorator : IWeapon
{
// 肚子里包裹着被装饰的武器对象
protected IWeapon _wrappedWeapon;
public WeaponDecorator(IWeapon weapon)
{
_wrappedWeapon = weapon;
}
// 默认行为:直接调用被包裹对象的方法(让子类去重写)
public virtual float GetDamage()
{
return _wrappedWeapon.GetDamage();
}
public virtual string GetDescription()
{
return _wrappedWeapon.GetDescription();
}
}
4. 实现具体的装饰器(各种 Buff 附魔)
// 具体的装饰器 A:火焰附魔
public class FireDecorator : WeaponDecorator
{
public FireDecorator(IWeapon weapon) : base(weapon) { }
public override float GetDamage()
{
// 在基础伤害上 + 5点火焰伤害
return base.GetDamage() + 5f;
}
public override string GetDescription()
{
return base.GetDescription() + " (+火焰伤害)";
}
}
// 具体的装饰器 B:暴击强化
public class CritDecorator : WeaponDecorator
{
public CritDecorator(IWeapon weapon) : base(weapon) { }
public override float GetDamage()
{
// 伤害翻倍!
return base.GetDamage() * 2f;
}
public override string GetDescription()
{
return base.GetDescription() + " [致命一击]";
}
}
5. 在 Unity 中使用它们(见证奇迹的时刻)
using UnityEngine;
public class PlayerWeaponSystem : MonoBehaviour
{
void Start()
{
// 1. 玩家获得了一把白板剑
IWeapon myWeapon = new BasicSword();
Debug.Log($"{myWeapon.GetDescription()} | 伤害: {myWeapon.GetDamage()}");
// 输出: 普通长剑 | 伤害: 10
// 2. 吃了一个火焰符文,用 FireDecorator 把它包起来!
myWeapon = new FireDecorator(myWeapon);
Debug.Log($"{myWeapon.GetDescription()} | 伤害: {myWeapon.GetDamage()}");
// 输出: 普通长剑 (+火焰伤害) | 伤害: 15
// 3. 又吃了一个暴击符文,继续用 CritDecorator 包起来!
myWeapon = new CritDecorator(myWeapon);
Debug.Log($"{myWeapon.GetDescription()} | 伤害: {myWeapon.GetDamage()}");
// 输出: 普通长剑 (+火焰伤害) [致命一击] | 伤害: 30
}
}
看!我们没有写任何类似 FireCritSword 的类,却完美实现了功能的无限自由组合!
二、 🤯 震撼 Unity 开发者的事实:你其实每天都在用装饰器!
作为 Unity 开发者,当你彻底理解装饰器模式后,你会恍然大悟:Unity 的 GameObject 和 Component (组件) 系统,本质上就是一种宏观上的装饰器架构!
- 基础对象: 一个空的
GameObject就像是一张白纸(基础组件)。 - 装饰过程:
- 你想让它受重力影响?给它 AddComponent 一个
Rigidbody(重力装饰器)。 - 你想让它发声?给它加上
AudioSource(声音装饰器)。 - 你想让它能发光?给它加上
Light(发光装饰器)。
Unity 摒弃了传统的“面向对象深度继承体系”(比如让玩家继承自物理类,物理类继承自渲染类),转而采用了“组合优于继承”的组件式设计。这与装饰器模式的核心哲学如出一辙!
💡 总结与建议
-
核心思想: 不用继承来扩展功能,而是把新功能做成“包装盒”,一层一层地套在基础对象外面。
-
适用场景:
-
RPG 游戏中的装备词条叠加、Buff / Debuff 系统。
-
角色的多种状态叠加(比如吃了无敌星,同时又喝了变大药水)。
-
需要根据不同条件动态计算数值(如复杂的防御力、攻击力、抗性公式)。
-
避坑指南: 装饰器模式虽然极其灵活,但如果包了太多层,在调试时(Debug)你想看最里面那个原始对象的状态会变得有些困难。因此,只在“需要动态且任意组合多个功能”时才使用它。