策略模式

一个开发小例子

想象一下, 假如有一天, 你在开发游戏, 需要设计敌人类Enemy作为父类, 同时下面有子类野猪Boar和鸟Bird, 每个敌人都有私有变量attackDamage用于记录造成的伤害值, 有一个公开方法Attack()用于造成伤害

classDiagram
    class Enemy{
        - attackDamage
        + Attack()
    }
    Enemy <|-- Boar
    class Boar{
        - attackDamage
        + Attack()
    }
    Enemy <|-- Bird
    class Bird{
        - attackDamage
        + Attack()
    }

但是后来加了需求, 做到某一关时候新加入了一种敌人喷火龙Dragon, 它在攻击玩家的时候不仅会有attackDamage的固定伤害, 玩家身上会起火, 导致每秒会有一个燃烧伤害burningDamage, 所以又写了一个方法叫做Burning()用于提供燃烧伤害, 在Attack()内调用燃烧方法, 现在类图变成了下面这样

classDiagram
    class Enemy{
        - attackDamage
        + Attack()
    }
    Enemy <|-- Boar
    class Boar{
        - attackDamage
        + Attack()
    }
    Enemy <|-- Bird
    class Bird{
        - attackDamage
        + Attack()
    }
    Enemy <|-- Dragon
    class Dragon{
        - attackDamage
        - burningDamage
        + Attack()
        + Burning()
    }

到此为止还算正常, 喷火龙这个敌人的特殊性只在它自己的Attack()Burning()中体现

但是后来又增加了需求, 又增加了一个可以喷火的敌人, 巫师, 他的攻击也可以产生一个固定伤害attackDamage和每秒burningDamage的燃烧伤害, 此时应该怎么办, 那么自然而然会产生下面两种想法

  1. 可以把Dragon里的逻辑复制一份到巫师的类中, 但是这样做很不优雅, 要是将来有20个会喷火的敌人的话, 这样的逻辑都得复制一遍, 并且如果燃烧的逻辑做出了修改, 那就得一个代码接一个的修改, 很是麻烦
  2. 把这部分攻击的逻辑提取出来, 制作一个Attack接口, 再写两个类实现接口常规攻击NormalAttack和燃烧攻击BurningAttack, 每个Enemy中增加一个成员变量Attack, 并给它赋值一个具体的实例, 这样具体的攻击逻辑就被分离了出去, 而且需求改变, 要做出修改的话也比较方便

现在类图变成了这样

classDiagram
    class Enemy{
        - attack
        + SetAttack(Attack attack)
        + Attack()
    }
    Enemy <|-- Boar
    class Boar{
        - attack
        + SetAttack(Attack attack)
        + Attack()
    }
    Enemy <|-- Bird
    class Bird{
        - attack
        + SetAttack(Attack attack)
        + Attack()
    }
    Enemy <|-- Dragon
    class Dragon{
        - attack
        + SetAttack(Attack attack)
        + Attack()
    }

    Enemy <|-- Wizard
    class Wizard{
        - attack
        + SetAttack(Attack attack)
        + Attack()
    }

    class Attack{
        - attackDamage
        + Attack()
    }

    Attack <|-- NormalAttack
    class NormalAttack{
        - attackDamage
        + Attack()
    }

    Attack <|-- BurningAttack
    class BurningAttack{
        - attackDamage
        - burningDamage
        + Attack()
        + Burning()
    }

现在不同的敌人只需要设置好自己的attack成员变量就可以实现不同的攻击逻辑

总结: 什么是策略模式

策略模式的核心思想是将不同的算法或行为封装到独立的类中, 然后通过一个通用接口来调用它们。

策略模式定义了一系列算法或策略, 并将每个算法封装在独立的类中, 使得它们可以互相替换。通过使用策略模式, 可以在运行时根据需要选择不同的算法, 而不需要修改代码

什么叫在运行时候选择不同的算法?

想象有一个游戏角色, 他可以使用不同的武器攻击敌人, 比如剑、弓箭和魔法。不同的武器有不同的攻击方式, 比如近战攻击、远程攻击、和范围攻击。策略模式允许在战斗中随时更换武器, 而不需要改变角色本身的代码
如果有新的武器和攻击方式, 那么不需要修改角色类中的代码, 而是新建立一个类实现Weapon接口, 在角色类中只需要修改成员变量weapon即可

下面是类图展示
(注意弓箭造成的伤害在Unity中一般把碰撞器组件加在射出去的箭上Arrow, 所以下面类图中造成伤害的武器写成了Arrow而非Bow弓)

classDiagram
    class Character{
        - weapon
        + SetWeapon(Weapon weapon)
        + Attack()
    }

    class Weapon{
        + takeDamage()
    }

    Weapon <|-- Sword
    class Sword{
        + takeDamage()
    }

    Weapon <|-- Arrow
    class Arrow{
        + takeDamage()
    }

    Weapon <|-- Magic
    class Magic{
        + takeDamage()
    }

想要更换攻击方式时, 只需要更换策略, 而不需要修改角色的代码逻辑。策略模式的好处是使得算法的选择与算法的实现分离, 提高了代码的灵活性和可维护性