返回

6.策略模式

定义一组算法,并封装每个算法,让它们可以彼此交换使用。策略模式让这些算法在客户端使用它们时更独立。

角色属性的计算需求

需求:

  • 玩家阵营的角色有等级属性,等级可通过“兵营升级”的方式提升,等级提升可以增加防守优势,这些优势包含:角色等级越高,“生命力”就越高,生命力会按照等级加成;被攻击时,角色等级越高,可以低于更多的攻击力。
  • 敌方阵营的角色攻击时,有一定的概率会产生暴击,当暴击发生时,会将“暴击值”作为武器的额外攻击力,让敌方阵营角色增加攻击优势。

错误示范:

较简单粗暴的方法,就是定义两种角色类型,在角色类发起攻击时根据根据当前角色类型选择攻击伤害计算方式。

//角色类型
public enum ENUM_Character
{
    Soldier = 0
    Enemy
}

//角色接口
public class Character
{
    //拥有一种武器
    protected Weapon m_weapon = null;
    
    //角色属性
    ENUM_Character m_CharacterType;
    int m_MaxHp = 0;
    int m_NowHp = 0;
    ...

    public void Attack(ICharacter theTartget)
    {
        //按角色类型判断是否加成额外攻击力
        switch(m_CharacterType)
        {
            case ENUM_Character.Solder:
                //不需要
                break;
            case ENUM_Character.Enemy:
                //按暴击概率返回攻击加成值
                int RandeValue = Random.Range(0,100);
                if(m_CritRate >= RandeValue)
                    AtkPlusValue = m_MaxHp*5;//血量的5倍值
                break;
        }
    }
}

存在的问题

  • 每个方法都针对“角色类型”进行属性计算,所以这3个方法,因此会增加维护的难度。
  • 同一类型的计算规则分散在角色类Character中,不易阅读和了解。

策略模式

定义一组算法,并封装每个算法,让它们可以彼此交换使用。策略模式让这些算法在客户端使用它们时更独立。

策略模式的说明

在策略模式中,不同的计算方式就是所谓的“算法”,而这些算法中的每一个都应该独立出来,将“计算细节”加以封装隐藏,并让它们成为一个“算法”类群组。客户端只需要根据情况来选择对应的”算法“类即可,至于计算方式和规则,客户端不需要去理会。

参与者的说明如下:

  • Strategy(策略接口类):提供“策略客户端”可以使用的方法。
  • ConcreteStretegyX(策略实现类):不同算法的实现。
  • Context(策略客户端):拥有一个Strategy类的对象引用,并通过对象引用获取想要的计算结果。

具体实现

	// 擁有Strategy物件的客戶端
	public class Context
	{
		Strategy m_Strategy = null;

		// 設定演算法
		public void SetStrategy( Strategy theStrategy  )
		{
			m_Strategy = theStrategy;
		}

		// 執行目前的演算法
		public void ContextInterface()
		{
			m_Strategy.AlgorithmInterface();
		}
	}

	// 演算法的共用介面, Context透過此介面呼叫 ConcreteStrategy所實作的演算法
	public abstract class Strategy
	{
		public abstract void AlgorithmInterface();
	}

	// 演算法A
	public class ConcreteStrategyA : Strategy
	{
		public override void AlgorithmInterface()
		{
			Debug.Log ("ConcreteStrategyA.AlgorithmInterface");
		}
	}

	// 演算法B
	public class ConcreteStrategyB : Strategy
	{
		public override void AlgorithmInterface()
		{
			Debug.Log ("ConcreteStrategyB.AlgorithmInterface");
		}
	}

	// 演算法C
	public class ConcreteStrategyC : Strategy
	{
		public override void AlgorithmInterface()
		{
			Debug.Log ("ConcreteStrategyC.AlgorithmInterface");
		}
	}

使用策略模式实现攻击计算

参与者说明如下:

  • ICharacterAttr:声明游戏内使用的角色属性、访问方法和声明攻击流程中所需要的方法,并拥有一个IAttrStrategy对象,通过该对象来调真正的计算公式。
  • IAttrStrategy:声明角色属性计算的接口方法,用来把ICharacterAttr与计算公式分离,让ICharacterAttr可轻易地更换计算策略。
  • EnemyAttrStrategy:实现地方阵营单位在攻击流程中所需的各项公式的计算
  • SoldierAttrStrategy:实现玩家阵营单位在攻击流程中所需的各项公式的计算。

使用策略模式的优点

  • 让角色属性变得好维护
  • 不必再针对角色类型编写程序代码
  • 计算公式的替换更为方便

策略模式与其他模式的区分

与状态模式的差别

  • State是在一群状态中进行切换,状态之间有对应和链接的关系;Strategy则是由一群没有任何关系的类所组成,不知彼此的存在。
  • State受限于状态机的切换规则,在设计初期就会定义所有可能的状态,就算后期追加也需要和现有的状态有所关联,而不是想加入就加入;Strategy是由封装计算算法而形成的一种设计模式,算法之间不存在任何依赖关系,有新增的算法就可以马上加入或替换。

与桥接模式的差别

相对策略模式,桥接模式要表达的内容要更多,结构也更加复杂。桥接模式表达的主要意义其实是接口隔离的原则,即把本质上并不内聚的两种体系区别开来,使得它们可以松散的组合,而策略在解耦上还仅仅是某一个算法的层次,没有到体系这一层次。

策略模式的应用

  • 有些角色扮演游戏(RPG)的属性系统,会使用“转换计算”的方式来获取角色最终要使用的属性。例如:玩家看到角色界面上只会显示“体力”“力量”“敏捷”……,但实际在运用攻击计算时,这些属性会被在转换为“生命力”“攻击力”“闪避率”。而之所以会这样设计的原因在于,该游戏有“职业”的设置,对于不同的“这也”,在计算转换时会有不同的转换方式,利用策略模式将这些转换公式独立出来是比较好的
  • 游戏角色操作载具时,会引用角色当前对该类型载具的累积时间,并将之转换为“操作性”,操作性越好,就越能控制该载具。而获取操作性的计算公式,也可以利用策略模式,将其独立出来。
  • 网络在线型游戏往往需要玩家注册账号,注册账号有多种方式,例如:OpenID、自建账号、随机产生等,通过策略模式可以将不同账号的注册方式独立为不同的登录策略。这样做,处理可以强化项目的维护,也可以方便转换到不同的游戏项目上,增加重复利用的价值。