(21)行为型模式——策略

行为型模式——策略(Strategy)

问题背景

当需要从一系列算法中动态选择一个时,考虑使用策略。在大部分有伤害系统的游戏中,都会区分物理伤害和魔法伤害。思考如下场景:单位A攻击单位B,发出抛射体P,P与B碰撞后调用MakeDamage方法造成伤害。这个方法的实现方式就是讨论的重点:是直接在方法内部实现伤害计算逻辑,还是将请求转发给一个专门计算伤害的对象?如果直接在内部实现,会让抛射体类变得臃肿,而且抛射体的职责应该是控制移动,而不是计算伤害。再者,如果以后出现了一种新的伤害,比如真实伤害,就要修改抛射体类,违背了开闭原则。所以,我们选择后者。

解决方案

这次我们要分离的对象是一个算法,或者说是一个策略,因此我们提取一个策略接口IStrategy,其中有一个Calculate方法负责计算。实现类PhysicalStrategy和ManaStrategy实现IStrategy接口,分别实现计算物理伤害和魔法伤害的逻辑。抛射体类聚合了一个策略对象,负责计算伤害。使用策略后的程序结构是这样的:
程序结构
图中Unit表示游戏中的单位,可以攻击、被攻击,Attack方法是物理攻击,会生成一个聚合了PhysicalStrategy的抛射体;UseSkill是魔法攻击,会生成一个聚合了ManaStrategy的抛射体。

这样,每个类就做到了轻量、各司其职,系统的结构更加清晰了。同时,由于接口的使用,新的伤害类型也非常容易扩展。

效果

  1. 提供了一系列可重用的算法,减少了重复代码。
  2. 用组合代替继承,获得了动态特性。
  3. 很好地分离了各个类的职责。

缺陷

由于策略提供的是一种算法,所以可能存在这种情况:从结果来看,两种策略没有任何不同,它们只是适用场景不同,比如插入排序和快速排序。这时用户就必须理解两种策略的实现细节才能正确使用它们,这在一定程度上破坏了类隐藏信息的特点。另外,在一些高性能场景,尤其是策略模式这种针对算法的设计模式,调用策略类的额外开销会产生严重的负面影响,这也是需要考虑的。

相关模式

  1. 享元:一些策略对象可以设计成享元。
  2. 单例:无状态的策略对象可以设计成单例。

实现

using System;

namespace Strategy
{
    class Client
    {
        public interface IStrategy
        {
            int Calculate(Projectile proj);
        }

        public class Unit
        {
            public int HP { get; set; }
            public int ATK { get; }
            public int DEF { get; }
            public int MDF { get; }
            public Unit(int hp, int atk, int def, int mdf)
            {
                HP = hp;
                ATK = atk;
                DEF = def;
                MDF = mdf;
            }
            public void Attack(Unit target)
            {
                Console.WriteLine("攻击");
                new Projectile(this, target, ATK, true).MakeDamage();
            }
            public void UseSkill(Unit target)
            {
                Console.WriteLine("使用技能");
                new Projectile(this, target, (int) (1.2 * ATK), false).MakeDamage();
            }
        }

        public class Projectile
        {
            public IStrategy strategy;
            public Unit Owner { get; }
            public Unit Target { get; }
            public int Damage { get; }
            public Projectile(Unit owner, Unit target, int damage, bool isPhysical)
            {
                Owner = owner;
                Target = target;
                Damage = damage;
                strategy = isPhysical ? new PhysicalStrategy() as IStrategy : new ManaStrategy() as IStrategy;
            }
            public void MakeDamage()
            {
                var damage = strategy.Calculate(this);
                Target.HP = Math.Max(Target.HP - damage, 0);
                Console.WriteLine($"目标受到了{damage}点伤害,剩余HP: {Target.HP}");
            }
        }

        public class PhysicalStrategy : IStrategy
        {
            public int Calculate(Projectile proj)
            {
                return Math.Max(proj.Damage - proj.Target.DEF, 1);
            }
        }

        public class ManaStrategy : IStrategy
        {
            public int Calculate(Projectile proj)
            {
                return Math.Max(proj.Damage - proj.Target.MDF, 1);
            }
        }

        static void Main(string[] args)
        {
            var striker = new Unit(100, 50, 50, 50);
            var victim = new Unit(200, 10, 20, 10);

            striker.Attack(victim); // HP=200-(50-20)=170
            striker.UseSkill(victim); // HP=170-(1.2x50-10)=120
        }
    }
}

运行结果

发布了27 篇原创文章 · 获赞 41 · 访问量 2050

猜你喜欢

转载自blog.csdn.net/DIAX_/article/details/104387435