状态模式(State)

First Post:

Last Update:

状态模式(State)

坏的代码——冗长,难读,判断分支过多

如何避免一个程序中写满了if和else分支判断呢?

如果一个程序的判断分支过多时,代表着该程序的责任过多了,无论任何状态的改变,都通过它单独来改变,这是糟糕的。

面向对象设计其实就是希望做到代码的责任分解。

当一个对象的内在状态改变时,允许改变其行为,这个对象看起来像是改变了其类。

状态模式主要解决的是当控制一个对象状态转换的条件表达式过于复杂的情况。把状态的判断逻辑转移到表示不同状态的一系列类当中,可以把复杂的判断逻辑简化。

状态模式结构图

状态模式类图

状态模式成员分析

Context上下文对象,该类维护一个ConcreteState子类的实例,表示当前的状态。
State抽象状态类,定义一个接口封装与Context特定状态相关的行为。
ConcreteState具体状态类,每一个子类实现一个与Context中一个状态相关的行为。

状态模式代码结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
代码实现:
abstract class State
{
public abstract void Handle(Context context);
}

class ConcreteState1 : State
{
public override void Handle(Context context)
{
context.State = new ConcreteStateB();
}
}

class ConcreteStateB : State
{
public override void Handle(Context context)
{//设置下一个状态为ConcreteState1
context.State = new ConcreteState1();
}
}

class Context
{
private State state;//定义一个状态的引用表示当前状态
public Context(State state)
{
//构造方法初始化当前状态
this.state = state;
}
public State State
{//可读写的状态属性,用于读取当前状态和设置新状态
get { return state; }
set { state = value; }
}
public void Request()
{//对请求作处理,设置下一状态。
state.Handle(this);
}
}

//客户端代码
Context context = new Context(new ConcreteState1());
context.Request();
context.Request();
context.Request();
context.Request();
//不断请求,context对象处理请求并更新状态

经典用例——游戏中敌人ai

1
2
3
4
5
6
7
8
9
10
11
12
13
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public interface Istate
{
public void OnStateEnter();

public void OnStateProcess();

public void OnStateExit();

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class States
{

}
public class AlienIdle : Istate
{
private StateManager stateManager;
public float time;
public AlienIdle(StateManager stateManager)
{
this.stateManager = stateManager;
}

public void OnStateEnter()
{

}

public void OnStateExit()
{

}

public void OnStateProcess()
{
Vector3 tarPos = stateManager.GetPlayerPos();
if ((tarPos - stateManager.transform.position).magnitude < 8.0f)
{
Debug.Log("切换到walk状态");
stateManager.ChangeState(stateType.walk);
}

}
}

public class AlienWalk : Istate
{
private StateManager stateManager;
private float timer;
private int direction;
public AlienWalk(StateManager stateMachine)
{
this.stateManager = stateMachine;
}

public void OnStateEnter()
{
//stateMachine._aiAnim.Play("walk");
stateManager._aiAnim.SetBool("walking",true);
if (stateManager.GetPlayerPos().x > stateManager.transform.position.x)
{
direction = 1;
}
else
{
direction = -1;
}
Debug.Log("进入了Walk状态");
timer = 1.0f;
}

public void OnStateExit()
{
stateManager._aiAnim.SetBool("walking", false);
}

public void OnStateProcess()
{
stateManager._aiRigid.velocity = new Vector2(direction*stateManager.speed,0);
timer -= Time.deltaTime;
if (timer < 0)
{
stateManager.ChangeState(stateType.idle);
}

}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
using System.Collections;
using System.Collections.Generic;
using UnityEngine;


public enum stateType
{
idle,
walk
}

public class StateManager : MonoBehaviour
{
public Animator _aiAnim;
public Rigidbody2D _aiRigid;
public Collider2D _aiCollider;
public float speed = 2.0f;

public Istate _currentState;//当前状态

public stateType _state;

public Dictionary<stateType, Istate> _stateMapping = new Dictionary<stateType, Istate>();//存放状态与枚举之间的映射值

private void Awake()
{
//为状态枚举注册对应的状态
_stateMapping.Add(stateType.idle, new AlienIdle(this));
_stateMapping.Add(stateType.walk, new AlienWalk(this));
//获取对应的组件和必要参数
_aiAnim = GetComponentInChildren<Animator>();
_aiRigid = GetComponent<Rigidbody2D>();
_aiCollider = GetComponent<Collider2D>();
_currentState = _stateMapping[stateType.idle];
}


private void Update()
{
_currentState.OnStateProcess();//调用处于状态中的处理函数
Filp();
}


public void ChangeState(stateType state)//切换到新的状态
{
if (_currentState != null)
{
_currentState.OnStateExit();
}
_currentState = _stateMapping[state];
_currentState.OnStateEnter();
}

#region 公用方法
//获取玩家位置
public Vector3 GetPlayerPos()
{
return PlayerController.Instance().transform.position;
}

//使得角色正确翻转
public void Filp()
{
if (_aiRigid.velocity.x > .1f)
{
transform.localScale = new Vector3(-1,1,1);
}else if (_aiRigid.velocity.x < -.1f)
{
transform.localScale = new Vector3(1, 1, 1);
}
}
#endregion
}

状态模式的好处

  • 将特定状态相关的行为局部化,并将不同状态的行为分割开来。
  • 将特定的状态相关的行为都放入一个对象中,由于所有与状态相关的代码都存在于ConcreteState中,所有通过定义新的子类可以很方便地增加新的状态和转换。
  • 消除了庞大的条件分支语句
  • 状态模式将各种状态转换的逻辑分布到State的子类之间,来减少相互间的依赖。

什么时候考虑用状态模式?

当一个对象的行为取决于他的状态,并且它必须在运行时刻根据状态改变它的行为时,就可以考虑使用状态模式。

枚举与状态模式+字典,天生一对。

1
2
3
4
5
6
enum StateType
{
idle,
patrol,
atk,
}