0x08.装饰器模式的另一个示例
装饰器模式是设计模式中比较绕的一个,因为它打破了我们平时习惯的“父类-子类”这种直线型的思维方式。
为了让你更容易懂,这次我们不搞抽象的数据,我们用一个非常形象的“俄罗斯套娃”比喻,结合“玩家受到伤害时的防御结算”来再举一个例子。
想象一下,装饰器模式就像是你大冬天出门穿衣服:
- 最核心的基础是你自己(一个肉体)。
- 穿上一件保暖内衣(第一层装饰器),它拦截了外面的冷空气,削弱了一点寒冷,然后传给你的身体。
- 再穿上一件羽绒服(第二层装饰器),它先拦截了最外层的暴风雪,削弱了大部分寒冷,再传给保暖内衣。
在代码中,每一层外衣都会先处理一下收到的伤害,然后再把剩下的伤害传递给里面的一层。
新示例:玩家的多层防御系统
假设玩家遭遇了 100 点伤害。玩家身上可能什么都没穿,也可能穿着护甲,甚至还有魔法护盾。我们可以用装饰器模式来处理这个伤害扣减的过程。
1. 定义核心规则 (定义“能承受伤害的实体”)
using UnityEngine;
// 任何能处理伤害的东西,都必须实现这个接口
public interface IDamageHandler
{
// 传入原始伤害,返回经过处理后最终剩下的伤害
float ProcessDamage(float rawDamage);
}
2. 定义最基础的内核 (没穿衣服的玩家)
这就是俄罗斯套娃最里面那个最小的娃娃。它没有任何减伤能力。
// 毫无防备的玩家肉体
public class NakedPlayer : IDamageHandler
{
public float ProcessDamage(float rawDamage)
{
Debug.Log($"[基础肉体] 承受了全部伤害: {rawDamage}");
// 没有减伤,直接返回全部伤害,用于最终扣血
return rawDamage;
}
}
3. 核心灵魂:防御装饰器基类 (通用的衣服)
这个类非常关键,它是一件“衣服”,它本身也是一个可以承受伤害的实体(实现了接口),但它肚子里还包着另一个人/衣服(持有接口引用)。
public abstract class DefenseDecorator : IDamageHandler
{
// 肚子里包裹着的下一层(可能是玩家肉体,也可能是另一件衣服)
protected IDamageHandler _wrappedHandler;
// 穿衣服的过程(把里层包进去)
public DefenseDecorator(IDamageHandler handlerToWrap)
{
_wrappedHandler = handlerToWrap;
}
// 衣服被打了,默认行为是直接把伤害穿透给下一层
// 这里设为 virtual,让具体的衣服去重写减伤逻辑
public virtual float ProcessDamage(float rawDamage)
{
return _wrappedHandler.ProcessDamage(rawDamage);
}
}
4. 具体的衣服 (各种防御 Buff)
现在我们来写两件真正的防御装备。
// 具体的装饰器 A:钢铁护甲(按比例减伤)
public class IronArmor : DefenseDecorator
{
public IronArmor(IDamageHandler handlerToWrap) : base(handlerToWrap) { }
public override float ProcessDamage(float rawDamage)
{
// 钢铁护甲可以抵消 50% 的伤害!
float reducedDamage = rawDamage * 0.5f;
Debug.Log($"[钢铁护甲] 抵挡了50%伤害,剩下的 {reducedDamage} 伤害穿透到了里面。");
// 把剩下的伤害传递给它包裹住的下一层
return base.ProcessDamage(reducedDamage);
}
}
// 具体的装饰器 B:魔法护盾(固定减伤)
public class MagicShield : DefenseDecorator
{
public MagicShield(IDamageHandler handlerToWrap) : base(handlerToWrap) { }
public override float ProcessDamage(float rawDamage)
{
// 魔法护盾可以固定吸收 20 点伤害
float reducedDamage = rawDamage - 20f;
if (reducedDamage < 0) reducedDamage = 0; // 伤害不能是负数
Debug.Log($"[魔法护盾] 吸收了20点伤害,剩下的 {reducedDamage} 伤害穿透到了里面。");
// 把剩下的伤害传递给下一层
return base.ProcessDamage(reducedDamage);
}
}
5. 见证奇迹的时刻 (在 Unity 中组装它们)
你看,我们不需要写什么 ArmorAndShieldPlayer 这种复杂的类,只需要把它们像穿衣服一样一层层套起来!
public class DamageTest : MonoBehaviour
{
void Start()
{
float incomingDamage = 100f;
Debug.Log($"--- 敌人砍来了 {incomingDamage} 点伤害! ---");
// 1. 创建最核心的玩家
IDamageHandler player = new NakedPlayer();
// 2. 给玩家穿上钢铁护甲 (把 player 包起来)
player = new IronArmor(player);
// 3. 给玩家再套上一层魔法护盾 (把穿着护甲的 player 再包一层)
player = new MagicShield(player);
// 现在这个 player 的结构是: [魔法护盾 [钢铁护甲 [玩家肉体]]]
// 4. 结算伤害!我们只需要对最外层(护盾)造成伤害
float finalDamageToTake = player.ProcessDamage(incomingDamage);
Debug.Log($"--- 最终结算:玩家肉体实际需要扣除的血量是 {finalDamageToTake} ---");
}
}
运行结果会是怎样的?
- 100点伤害先打在最外层魔法护盾上,吸收20点,剩 80 点传给里面。
- 80点伤害打在里面的钢铁护甲上,抵消50%,剩 40 点传给里面。
- 40点伤害打在最里面的基础肉体上,最终返回 40。
重新理解装饰器
通过这个“一层层减伤过滤”的例子,你是不是发现了装饰器模式最大的魅力?
它允许你动态地在运行时给对象添加职责。如果玩家在游戏中途突然被怪物施加了“易伤 Debuff(受到伤害增加20%)”,你只需要再写一个 VulnerabilityDecorator,然后把它套在最外面就行了,原有的护甲、护盾代码一行都不需要修改!