0x09.装饰器模式移除某个节点的问题
纯正的“装饰器模式”最大的痛点。
在标准的装饰器模式中,把对象一层层包起来(添加功能)非常容易,但想要从中间抽掉某一层(移除功能),是非常困难甚至丑陋的。
想象一下那个“俄罗斯套娃”的例子:最外面是魔法护盾,中间是钢铁护甲,最里面是玩家肉体。如果此时“钢铁护甲”的耐久度没了,碎了,你怎么在不破坏最外层“魔法护盾”的情况下,把中间那层抽出来?
最外层的护盾只知道它的下一层是一个 IDamageHandler,它根本不知道里面是不是护甲。
为了解决这个问题,在实际的游戏开发中,我们通常有以下三种策略:
方法一:简单粗暴的“推倒重来” (Rebuild 策略)
既然从中间抽出一层很麻烦,那我们就把所有衣服脱光,重新穿一遍(跳过坏掉的那件)。
在实际代码中,我们可以用一个“列表”来记录玩家当前拥有的所有 Buff。当某个 Buff 需要移除时,我们不是去修改装饰器的链条,而是更新这个列表,然后重新生成整个装饰器链。
using System.Collections.Generic;
using UnityEngine;
public class PlayerDefenseSystem : MonoBehaviour
{
// 记录玩家当前所有的“附魔/Buff”标识
private List<string> activeBuffs = new List<string>();
// 核心玩家肉体
private IDamageHandler basePlayer = new NakedPlayer();
// 当前正在生效的最终防御链
private IDamageHandler currentDefenseChain;
void Start()
{
// 初始状态
currentDefenseChain = basePlayer;
}
// 添加 Buff
public void AddBuff(string buffName)
{
if (!activeBuffs.Contains(buffName))
{
activeBuffs.Add(buffName);
RebuildDefenseChain(); // 重新穿衣服
}
}
// 移除 Buff (解决你的痛点)
public void RemoveBuff(string buffName)
{
if (activeBuffs.Contains(buffName))
{
activeBuffs.Remove(buffName);
RebuildDefenseChain(); // 重新穿衣服
}
}
// 核心逻辑:根据当前的 Buff 列表,从内到外重新包装一次
private void RebuildDefenseChain()
{
// 1. 先回到最基础的肉体状态
currentDefenseChain = basePlayer;
// 2. 根据列表,一层一层把衣服套上去
foreach (string buff in activeBuffs)
{
if (buff == "IronArmor")
{
currentDefenseChain = new IronArmor(currentDefenseChain);
}
else if (buff == "MagicShield")
{
currentDefenseChain = new MagicShield(currentDefenseChain);
}
}
Debug.Log("防御系统已重构,当前剩余 Buff 数量:" + activeBuffs.Count);
}
}
优点: 代码极其干净,绝对不会出现链条断裂引发的空指针异常。
缺点: 每次添加/删除 Buff 都要重新实例化(new)所有的装饰器,如果 Buff 极其频繁地改变,可能会产生一些 GC(垃圾回收)压力。
方法二:给装饰器增加“脱钩”功能 (链表解绑)
如果你不想推倒重来,就必须打破一点点面向对象的封闭性,让每一层装饰器都有能力“剥离”自己或者剥离自己的下一层。这本质上就是把装饰器链当成一个单向链表来操作。
// 修改后的接口,增加一个剥离自身的方法
public interface IDamageHandler
{
float ProcessDamage(float rawDamage);
// 尝试从链条中移除指定的类型的装饰器,并返回缝合后的新链条
IDamageHandler RemoveDecorator(System.Type typeToRemove);
}
这种写法的具体实现会非常烧脑和绕(涉及到链表节点的删除和重新指向),对于初学者来说容易写出 Bug,而且违背了装饰器模式“各司其职”的初衷,极其不推荐在日常开发中使用。
方法三:🌟 行业标准答案(修改器列表模式)
这里我要对你坦诚相见:在真实的商业 Unity 游戏开发中,针对频繁增加和移除的 Buff 系统,我们几乎不用纯粹的 GoF 装饰器模式!
我们会使用一种变体,通常被称为 修改器模式 (Modifier Pattern),它融合了策略模式和一点点责任链模式的思想。
相比于“相互嵌套”,它直接用一个 List 来管理所有的“衣服”,想要脱哪件,直接从 List 里面 Remove 掉就行了。
这才是你真正在 Unity 中应该写出的代码:
using System.Collections.Generic;
using UnityEngine;
// 1. 定义一个防御修改器接口(不再相互包裹了)
public interface IDefenseModifier
{
float ModifyDamage(float currentDamage);
}
// 2. 具体的修改器
public class IronArmorModifier : IDefenseModifier
{
public float ModifyDamage(float currentDamage) { return currentDamage * 0.5f; }
}
public class MagicShieldModifier : IDefenseModifier
{
public float ModifyDamage(float currentDamage) { return Mathf.Max(0, currentDamage - 20f); }
}
// 3. 玩家的防御管理器
public class ModernPlayerDefense : MonoBehaviour
{
// 直接用一个 List 装所有的 Buff !(想要移除哪个,太简单了)
private List<IDefenseModifier> modifiers = new List<IDefenseModifier>();
public void AddModifier(IDefenseModifier mod)
{
modifiers.Add(mod);
}
// 【解决你的问题】极其轻松地移除特定 Buff
public void RemoveModifier(IDefenseModifier mod)
{
modifiers.Remove(mod);
}
// 结算伤害时,遍历 List 即可
public void TakeDamage(float rawDamage)
{
float finalDamage = rawDamage;
// 像流水线一样,把伤害依次经过所有的修改器
foreach (var mod in modifiers)
{
finalDamage = mod.ModifyDamage(finalDamage);
}
Debug.Log($"原始伤害 {rawDamage},经过 {modifiers.Count} 层过滤后,最终承受伤害 {finalDamage}");
}
}
💡 总结时刻
- 如果你是在面试,或者写静态的功能扩展(比如配置不同品种的咖啡和配料),回答“纯正的装饰器模式”。
- 如果你遇到了需要在运行时频繁、随机地移除某一个中间层(比如游戏里的动态 Buff/Debuff 系统),赶紧丢掉纯装饰器模式,拥抱修改器列表 (List of Modifiers)。设计模式是死的,如何根据实际需求灵活变通,才是高级程序员的智慧。