装饰器模式是设计模式中比较绕的一个,因为它打破了我们平时习惯的“父类-子类”这种直线型的思维方式。

为了让你更容易懂,这次我们不搞抽象的数据,我们用一个非常形象的“俄罗斯套娃”比喻,结合“玩家受到伤害时的防御结算”来再举一个例子。

想象一下,装饰器模式就像是你大冬天出门穿衣服:

  1. 最核心的基础是你自己(一个肉体)。
  2. 穿上一件保暖内衣(第一层装饰器),它拦截了外面的冷空气,削弱了一点寒冷,然后传给你的身体。
  3. 再穿上一件羽绒服(第二层装饰器),它先拦截了最外层的暴风雪,削弱了大部分寒冷,再传给保暖内衣。

在代码中,每一层外衣都会先处理一下收到的伤害,然后再把剩下的伤害传递给里面的一层。


新示例:玩家的多层防御系统

假设玩家遭遇了 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} ---");
    }
}

运行结果会是怎样的?

  1. 100点伤害先打在最外层魔法护盾上,吸收20点,剩 80 点传给里面。
  2. 80点伤害打在里面的钢铁护甲上,抵消50%,剩 40 点传给里面。
  3. 40点伤害打在最里面的基础肉体上,最终返回 40。

重新理解装饰器

通过这个“一层层减伤过滤”的例子,你是不是发现了装饰器模式最大的魅力?

它允许你动态地在运行时给对象添加职责。如果玩家在游戏中途突然被怪物施加了“易伤 Debuff(受到伤害增加20%)”,你只需要再写一个 VulnerabilityDecorator,然后把它套在最外面就行了,原有的护甲、护盾代码一行都不需要修改!