状态(State)模式,又名状态对象(Objects for States)模式,它允许一个对象在内部状态改变时改变它的行为。对象看起来似乎改变了它的类。
状态模式的应用场景:
1、一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变自己的行为。
2、一个操作中含有庞大的多分支的条件语句,而这些分支条件依赖于对象的状态。
Unity3D里面有一个很有意思的东西,叫做AnimatorController,它可以根据用户设置的条件和参数的不同表现出不同的动画,例如设定速度为0为待机动作,小于5为行走动作,大于5为跑步动作。虽然具体实现不得而知,但无疑这是状态模式的一种应用。
本文要举这么一个例子:
有这样一个游戏,主角身体里住着三个灵魂,它可以切换成对应的三种形态,来施放不同的技能。(这与小话设计模式(七)桥接模式是同一个例子,文末会讨论他们的区别)
灵魂接口定义:
public interface ISoul
{
void CastSkill(Hero hero);
}
灵魂的枚举类型定义:
public enum SoulState
{
Mage,
Hunter,
Warrior,
}
英雄定义:
public class Hero
{
ISoul _soul;
public Hero()
{
_soul = Warrior.GetInstance ();
}
public SoulState soulState {get;set;}
public void ChangeState(ISoul soul)
{
_soul = soul;
}
public void CastSkill()
{
_soul.CastSkill (this);
}
}
可以看到,Hero的构造函数里面调用了Warrior类的GetInstance这个方法,熟悉设计模式的同学应该可以推测出,本文把灵魂定义为单例类。因为我们希望状态类的内部是要保存一些自身的数值的,例如在CastSkill之后,这个技能会进入CD,那么立刻切换灵魂,并立刻切换回来的时候,我们希望这个灵魂还在CD当中(CD足够长)。
本文借用了小话设计模式(一)单例模式的写法,来实现单例:
public class TSingleton<T> where T : class, new() {
static T instance;
static readonly object syncObj = new object();
public static T GetInstance()
{
if (instance == null) {
lock (syncObj) {
if (instance == null) {
instance = new T ();
}
}
}
return instance;
}
public static void PurgeInstance()
{
instance = null;
}
}
每个灵魂的定义:
public class Mage : TSingleton<Mage>, ISoul
{
public void CastSkill(Hero hero)
{
if (hero.soulState == SoulState.Hunter) {
hero.ChangeState (Hunter.GetInstance ());
hero.CastSkill ();
} else if (hero.soulState == SoulState.Warrior) {
hero.ChangeState (Warrior.GetInstance ());
hero.CastSkill ();
} else {
Console.WriteLine ("Summon a box");
}
}
}
public class Hunter : TSingleton<Hunter>, ISoul
{
public void CastSkill(Hero hero)
{
if (hero.soulState == SoulState.Mage) {
hero.ChangeState (Mage.GetInstance ());
hero.CastSkill ();
} else if (hero.soulState == SoulState.Warrior) {
hero.ChangeState (Warrior.GetInstance ());
hero.CastSkill ();
} else {
Console.WriteLine ("Fire arrow");
}
}
}
public class Warrior : TSingleton<Warrior>, ISoul
{
public void CastSkill(Hero hero)
{
if (hero.soulState == SoulState.Hunter) {
hero.ChangeState (Hunter.GetInstance ());
hero.CastSkill ();
} else if (hero.soulState == SoulState.Mage) {
hero.ChangeState (Mage.GetInstance ());
hero.CastSkill ();
} else {
Console.WriteLine ("Power of Thor");
}
}
}
这里我们也可以加入一些其他的判断,例如英雄血量小于20的时候,必须切换为Warrior灵魂。
使用:
Hero hero = new Hero ();
hero.soulState = SoulState.Mage;
hero.CastSkill ();
本文使用了小话设计模式(七)桥接模式的例子,但是实现方法却不太相同。桥接模式是由Hero(Handle手)来完成切换Soul(Body体)的工作,而状态模式中Hero(Context环境)只是设置了一些参数(soulState或者hp),而Soul(State状态)自身负责完成切换的工作。
状态模式的优点:
1、它将特定状态下的相关行为局部化,将不同的状态行为分割开来。
2、它似的状态转换显式化。
3、State对象可以被共享,前提是State对象没有实例变量,即它们没有内部状态(本文示例不符合这个条件)。
缺点:
1、每个状态都会产生一个新类,这样会增加维护成本。
2、因为C#不存在友元,所以Hero的ChangeState方法不得不暴露出来,这样破坏了封装性(虽然友元本身也是破坏封装性的存在)。