一.引入问题
在软件开发过程中,应用程序可能会根据不同的情况作出不同的处理。最直接的解决方案是将这些所有可能发生的情况全都考虑到。然后使用判断语句来做状态判断来进行不同情况的处理。但是对复杂状态的判断就显得“力不从心了”。随着增加新的状态或者修改一个状态导致判断语句的增多或者修改可能会引起很大的修改,而且程序的可读性,扩展性也会变得很弱。维护也会很麻烦。此时我们的状态模式就孕育而生了。
而状态模式的作用就是:解决当控制一个对象状态转换的条件表达式过于复杂时,把状态的判断逻辑转移到表示不同的一系列类当中,把复杂的逻辑判断简单化,其意图是让一个对象在其内部状态改变的时候其行为也随之改变,是一种对象行为型模式
定义
当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。这个有点晦涩的定义后面总结的时候再细讲。
UML图
StateManager:状态管理者,它的作用是维护一个当前状态,并将与状态相关的操作委托给当前状态对象来处理。
State:抽象状态,用于封装状态的行为和属性,简单来说就是提取各状态的共同行为和属性。
StateA:具体状态,实现抽象状态的具体行为。
具体示例:
下面我们以游戏中人物的人物为例,实现人物的状态机。
StateManager(状态管理者):
using System;
using System.Collections.Generic;
using UnityEngine;
//人物行为枚举
public enum Actions
{
NullAction = 0,
SeeEnemy,//看到敌人
NoEnemy,//失去敌人
CanAttack//攻击敌人
}
//人物状态ID
public enum StateID
{
NullState,
Idle,//站立
Chase,//追赶(奔跑)
Attack,//攻击
}
public class StateManager
{
private List<State> states = new List<State>();
private State mCurrent;
public State CurrentState
{
get
{
return mCurrent;
}
}
public void AddState(State state)
{
if (state==null)
{
Debug.LogError("state 不能为空null");
return;
}
else
{
if (states.Count==0)
{
states.Add(state);
mCurrent = state;
mCurrent.Enter();
return;
}
foreach (State item in states)
{
if (item == state)
{
Debug.LogError("states 已经存在state");
return;
}
}
states.Add(state);
}
}
public void AddState(params State[] states)
{
foreach (State item in states)
{
AddState(item);
}
}
public void DeleteState(StateID stateID)
{
if (states.Count==0)
{
Debug.LogError("states为空");
}
foreach (State item in states)
{
if (item.StateID== stateID)
{
states.Remove(item);
return;
}
}
Debug.LogError("states 不存在" + stateID.ToString());
}
public void ChangeState(Actions action)
{
if (action== Actions.NullAction)
{
Debug.LogError("action 不能为空");
}
StateID nextstate = mCurrent.GetNextState(action);
if (nextstate == StateID.NullState)
{
Debug.LogError("nextstate为空");
return ;
}
foreach (State item in states)
{
if (item.StateID== nextstate)
{
mCurrent.Leave();
mCurrent = item;
item.Enter();
return;
}
}
Debug.LogError(mCurrent+"没有"+ action);
}
public void print(string riZhi)
{
Debug.Log(riZhi);
}
}
人物抽象状态:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
public abstract class State
{
private Dictionary<Actions, StateID> actionDic = new Dictionary<Actions, StateID>();
private StateID mstateID;
protected StateManager stateFSM;
public State(StateManager stateManager,StateID currentState)
{
stateFSM = stateManager;
mstateID = currentState;
}
public StateID StateID
{
get
{
return mstateID;
}
}
public StateID GetNextState(Actions action)
{
if (action==Actions.NullAction)
{
Debug.LogError("action不能为空");
return StateID.NullState;
}
if (actionDic.ContainsKey(action)==false)
{
Debug.LogError(mstateID+"不包含:" + action);
return StateID.NullState;
}
return actionDic[action];
}
public abstract void Enter();
public abstract void Leave();
public abstract void UpdateState();
public void AddState(Actions action,StateID stateID)
{
if (action == Actions.NullAction)
{
Debug.LogError("action不能为空");
return ;
}
if (actionDic.ContainsKey(action) == true)
{
Debug.LogError("actionDic已包含:" + action);
return ;
}
actionDic.Add(action, stateID);
}
public void DeleteState(Actions action)
{
if (action == Actions.NullAction)
{
Debug.LogError("action不能为空");
return;
}
if (actionDic.ContainsKey(action) == false)
{
Debug.LogError("actionDic没有包含:" + action);
return;
}
actionDic.Remove(action);
}
}
人物站立状态:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
public class IdleState:State
{
public IdleState(StateManager stateManager, StateID currentState) : base(stateManager, currentState)
{
}
public override void Enter()
{
stateFSM.print("进入IdleState");
}
public override void Leave()
{
stateFSM.print("离开IdleState");
}
public override void UpdateState()
{
if (StartClient.command=="触发从Idle状态转换为Chase状态的条件")
{
stateFSM.print("触发从Idle状态转换为Chase状态的条件");
stateFSM.ChangeState(Actions.SeeEnemy);
}
}
}
人物追赶状态:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
public class ChaseState:State
{
public ChaseState(StateManager stateManager, StateID currentState) : base(stateManager, currentState)
{
}
public override void Enter()
{
stateFSM.print("进入ChaseState");
}
public override void Leave()
{
stateFSM.print("离开ChaseState");
}
public override void UpdateState()
{
if (StartClient.command == "触发从Chase状态转换为Attack状态的条件")
{
stateFSM.print("触发从Chase状态转换为Attack状态的条件");
stateFSM.ChangeState(Actions.CanAttack);
}
if (StartClient.command == "触发从Chase状态转换为Idle状态的条件")
{
stateFSM.print("触发从Chase状态转换为Idle状态的条件");
stateFSM.ChangeState(Actions.NoEnemy);
}
}
}
人物攻击状态:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
public class AttackState:State
{
public AttackState(StateManager stateManager, StateID currentState):base(stateManager,currentState)
{
}
public override void Enter()
{
stateFSM.print("进入AttackState");
}
public override void Leave()
{
stateFSM.print("离开AttackState");
}
public override void UpdateState()
{
if (StartClient.command == "触发从Attack状态转换为Chase状态的条件")
{
stateFSM.print("触发从Attack状态转换为Chase状态的条件");
stateFSM.ChangeState(Actions.SeeEnemy);
}
if (StartClient.command == "触发从Attack状态转换为Idle状态的条件")
{
stateFSM.print("触发从Attack状态转换为Idle状态的条件");
stateFSM.ChangeState(Actions.NoEnemy);
}
}
}
客户端示例:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
public class StartClient:MonoBehaviour
{
public static string command;
public StateManager playerStateFSM;
void Start()
{
playerStateFSM = new StateManager();
IdleState idlestate = new IdleState(playerStateFSM, StateID.Idle);
idlestate.AddState(Actions.SeeEnemy, StateID.Chase);
ChaseState chaseState = new ChaseState(playerStateFSM, StateID.Chase);
chaseState.AddState(Actions.NoEnemy, StateID.Idle);
chaseState.AddState(Actions.CanAttack, StateID.Attack);
AttackState attackState = new AttackState(playerStateFSM, StateID.Attack);
attackState.AddState(Actions.NoEnemy, StateID.Idle);
attackState.AddState(Actions.SeeEnemy, StateID.Chase);
playerStateFSM.AddState(idlestate, chaseState, attackState);
Invoke("SendCommand1", 1);
Invoke("SendCommand2", 2);
Invoke("SendCommand3",3);
Invoke("SendCommand4",4);
}
public void SendCommand1()
{
command = "触发从Idle状态转换为Chase状态的条件";
}
public void SendCommand2()
{
command = "触发从Chase状态转换为Attack状态的条件";
}
public void SendCommand3()
{
command = "触发从Attack状态转换为Chase状态的条件";
}
public void SendCommand4()
{
command = "触发从Chase状态转换为Idle状态的条件";
}
private void Update()
{
playerStateFSM.CurrentState.UpdateState();
}
}
总结:
优点:
1.状态模式将与特定状态相关的行为局部化到一个状态中,并且将不同状态的行为分割开来,满足“单一职责原则”。就像上面的这个例子一样,人物有很多不同的行为–站立,追赶,攻击…,并且这些行为内部是复杂的,而状态模式把这些行为对应到特定的相关状态,也就是我们上面代码所写的IdleState,ChaseState,AttackState等具体状态,把这些行为的复杂逻辑放到对应的状态进行处理。当当前的状态满足切换条件时也就是UpdateState的判断逻辑成立,我们通过状态管理者StateManager改变当前状态,而在进入新状态时就可以执行相关的行为切换逻辑,通常在Enter()方法里面实现。这也是定义:“当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类”的具体表现。
2.它减少对象间的相互依赖。将不同的状态提取成独立的对象中会使得状态转换变得更加明确,且减少对象间的相互依赖。观察上面的代码,我们可以很容易的发现IdleState,ChaseState,AttackState这些底层状态并不存在依赖关系,它们都依赖于抽象State,而我们的高层状态管理逻辑StateManager并不依赖于具体的状态,它也依赖与我们的抽象State,这样我们修改StateManager的几率就大大降低了,这其实就是我们的依赖倒置原则,具体的依赖倒置原则的内容可以看我写的这篇文章。
3.有利于程序的扩展。通过定义新的子类很容易地增加新的状态和转换,也就是我们的开闭原则。
缺点:
1.状态模式的使用必然会增加系统的类与对象的个数。
2.状态模式的结构与实现都较为复杂,如果使用不当会导致程序结构和代码的混乱。
如果本文有什么错误还望指出,如果您觉得写的不错,欢迎三连支持。