C#设计模式之策略模式

上篇博客分析了游戏中的单例模式,这篇博客分析一下游戏中使用到的策略模式。



策略模式

定义

定义了算法族,分别封装起来,让他们之间可以相互替换,此模式的算法可以独立于使用它的客户。[1]P24

可以将算法族理解为同一个算法的不同变体,典型的策略模式其实就是客户可以使用同一个算法的不同变体,使用的过程中可以更换。

适用性

以下情况可以使用策略模式
1. 需要使用一个算法的不同变体,如本文的游戏,Hero会使用不同的行为。
2. 许多相关的类仅仅行为有异,策略模式提供了一种用多个行为中的一个行为配置一个类的方法

上面对策略模式的描述说的可能比较抽象,下面我们还是结合游戏分析一下策略模式。

游戏中的客户与算法族

游戏中的客户就是角色(Roles),而算法族(同一个算法的不同变体)就是发射子弹的行为。
打开VS工程中的类图ClassDiagram1.cd,我们可以看到。

客户(所有角色)
客户
这些角色,包括英雄,怪兽。双击Roles和Hero可以看到源码:

 public abstract class Roles : RoAndMi
    {
        //拥有发射子弹的行为
        protected FireBehavior fireBehavior;
        //更换发射子弹的行为
        public void SetFireBehavior(FireBehavior fireBehavior)
        {
            this.fireBehavior = fireBehavior;
        }
        /// <summary>
        /// 调用fireBehavior.Fire()实现真正的发射
        /// </summary>
        public  void Fire()
        {
            fireBehavior.Fire();
        }
        ...
    }
public class Hero : Roles
    {
       public Hero(int x, int y, int xspeed, int yspeed, int life, bool good)
            : base(x, y, myImage.Width, myImage.Height, xspeed, yspeed, life, good)
        {
            blb = new BloodBar(x,y, life);
            //Hero拥有FireOneMissilesByHero行为
            SetFireBehavior(new FireOneMissilesByHero(this));

        }
   }

算法族(角色的行为)
算法族

public abstract class FireBehavior
    {
        protected Roles role;//哪个角色的射击
        public abstract void Fire();
    }
	//Hero拥有的行为
 class FireOneMissilesByHero : FireBehavior
    {
        public FireOneMissilesByHero(Roles r)
        {
            this.role = r;
        }
        //工厂方法,生产子弹
        public override void Fire()
        {
            if (!role.Live)
            {
                return;
            }
            HitCheck.GetInstance().AddElement(new MissileHero(HitCheck.GetInstance().MyHero, 20, 20, HitCheck.GetInstance().MyHero.Good, MissileDirection.U, 10));
        }
    }

类图

看一下他们的类图,会更加清晰的看到他们之间的关系

类图

分析类图
游戏中所有角色都有个发射子弹的行为FireBehavior(继承自Roles),但是每个角色拥有的行为又不一样,如EnemyOne拥有FireMissileOneByEnemy,Hero拥有FireOneMissilesByHero,并且客户在程序运行过程中可以更换行为,如Hero在程序运行过程中可以通过调用SetFireBehavior方法更换发射子弹的行为为FireThreeMissilesByHero,也就是说Hero使用了算法FireBehavior的不同变体,同时也可以更换行为,实现了同一算法不同变体的互换。

Hero更换发射行为
Hero更换发射行为的代码在HitCheck中,游戏中,当英雄的经验值>100后,Hero装备升级,能够同时发射3个子弹,通过SetFireBehavior方法更换行为(同一算法的不同变体)

//更换行为,升级装备
 if (myHero.score > 100)
            myHero.SetFireBehavior(new FireThreeMissilesByHero(myHero));//策略模式

这里为什么没有使用接口,而使用了抽象类
在游戏的实现过程中,发射子弹的行为与角色相关,所以在发射的时候,需要先判断是哪个角色,所以FireBehavior里面需要成员变量Roles,而接口是不能包含成员变量的,所以需要使用抽象类。仔细分析一下游戏的源码就会非常清楚为什么用抽象类了。
关于在设计过程中优先采用抽象类还是接口,这个问题要考虑很多东西,我不敢妄作评论,在具体设计过程中,我个人更加倾向于优先采用抽象类,而不是接口。

游戏中为什么要用策略模式

游戏中,英雄和敌人有个发射子弹的行为FireBehavior(继承自Roles),但是每个角色的行为都不一样,而且同一个角色在游戏中的发射子弹的行为也会发生变化,如英雄一开始每次只能发射一颗子弹,后来由于经验值增加,每次可以发射多个子弹,发射行为在游戏中会发生变化,在程序中需要更换行为,如果以后游戏要升级,角色会拥有更多的行为,如果使用策略模式,将不同的发射子弹的行为看成是FireBehavior的不同变体,让他们可以相互替换,那么以后升级游戏将会变得很容易。
在最初的版本中,是没有用策略模式的,也没有一种在程序中使用设计模式的意识,但是后来由于对设计模式的理解逐渐加深,自己也在思考,能否在游戏中加入设计模式,而且这个游戏非常适合加入策略模式,就这样,后面就把游戏给修改了。如果看过游戏的源码,就可以发现,以前的角色的行为都是作为角色类的一个方法,直接写在角色类里面的,如Hero源码中:

		//public override void fire()//实际发射
        //{
        //    if (!live)
        //    {
        //        return;
        //    }
        //    HitCheck.GetInstance().AddElement(new MissileHero(this, 20, 20, this.good, MissileDirection.LUU, 10));
        //    HitCheck.GetInstance().AddElement(new MissileHero(this, 20, 20, this.good, MissileDirection.U, 10));
        //    HitCheck.GetInstance().AddElement(new MissileHero(this, 20, 20, this.good, MissileDirection.RUU, 10));

        //}

用了策略模式后,将这些都注释掉了,将所有角色的行为都抽取出来了,封装成算法族了。使用策略模式,能够降低客户和算法族之间的耦合性,能够使系统具有良好的扩展性和维护性。

OO原则

针对接口编程,而不是针对实现编程

其实整个设计模式的一个核心思想就是针对接口编程。这里的接口,并不是java或者C#中的interface,这里的接口其实指的是超类型,包括接口和抽象类,核心思想就是多态。

看一下上面的类图
类图
Roles的成员变量fireBehavior

		 //使用发射子弹的行为
        protected FireBehavior fireBehavior;

和SetFireBehavior方法:

		public void SetFireBehavior(FireBehavior fireBehavior)
        {
            this.fireBehavior = fireBehavior;
        }

我们可以发现,fireBehavior是抽象类型,SetFireBehavior参数也是抽象类型,这样做的一个好处就是,调用SetFireBehavior方法的时候,可以将任何一个FireBehavior的子类作为参数,如英雄初始行为为SetFireBehavior(new FireOneMissilesByHero(this))
当经验值>100,更换发射子弹行为myHero.SetFireBehavior(new FireThreeMissilesByHero(myHero)),最后统一通过FireBehavior中的Fire()方法实现多态调用,而不用知道具体是什么类型,这样可以实现在程序运行过程中更换行为,增加系统的扩展性。


参考文献

[1] 《Head First设计模式(中文版)》 ,中国电力出版社
[2] 《设计模式:可复用面向对象软件的基础》(著名的GOF设计模式),机械工业出版社


非常感谢您的阅读,如果您觉得这篇文章对您有帮助,欢迎扫码进行赞赏。
这里写图片描述

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值