状态模式
状态模式是一种行为设计模式,它允许对象在其内部状态改变时改变其行为,使对象看起来像是修改了它的类。
状态模式是状态机的一种实现方式即可。状态机又叫有限状态机,它有 3 个部分组成:状态、事件、动作。其中,事件也称为转移条件。事件触发状态的转移及动作的执行。不过,动作不是必须的,也可能只转移状态,不执行任何动作。
核心概念
状态模式将特定状态相关的行为封装在独立的类中,并将请求委托给当前状态对象。当对象的内部状态改变时,它会改变自己的行为,而不需要修改原有类。
主要组件
Context(上下文):
维护一个具体状态对象的实例
定义客户端感兴趣的接口
将状态相关的操作委托给当前状态对象
State(状态):
定义一个接口,封装与Context的一个特定状态相关的行为
ConcreteState(具体状态):
实现State接口
每个具体状态类实现与Context的一个状态相关的行为
实现方式
总结了三种实现方式。
第一种实现方式叫分支逻辑法。利用 if-else 或者 switch-case 分支逻辑,参照状态转移图,将每一个状态转移原模原样地直译成代码。对于简单的状态机来说,这种实现方式最简单、最直接,是首选。
第二种实现方式叫查表法。对于状态很多、状态转移比较复杂的状态机来说,查表法比较合适。通过二维数组来表示状态转移图,能极大地提高代码的可读性和可维护性。
第三种实现方式叫状态模式。对于状态并不多、状态转移也比较简单,但事件触发执行的动作包含的业务逻辑可能比较复杂的状态机来说,我们首选这种实现方式。
UML类图
┌─────────────┐ ┌─────────┐
│ Context │<>---->│ State │
└─────────────┘ └─────────┘
▲ △
│ │
│ ┌────┴─────┐
│ │ │
┌─────────────┐ ┌─────────┐┌─────────┐
│Client(客户端)│ │StateA ││StateB │
└─────────────┘ └─────────┘└─────────┘
代码示例(C#)
namespace DesignPatternsPractice.Src.Behavioral.StateDesignPattern3
{
/// <summary>
/// 模拟一个超级玛丽的状态切换状态机
/// 使用状态模式
///
/// </summary>
public class IEnemyState3
{
static void Main()
{
Mario mario = new Mario();
// 模拟游戏逻辑
mario.UseMushrooms();
mario.UseMushrooms();
mario.UseMushrooms();
mario.TakeDamage();
mario.TakeDamage();
mario.TakeDamage();
mario.UseMushrooms();
}
}
public class Mario
{
private MarioState _currentState;
public Mario()
{
_currentState = new CommonState();
}
public void SetState(MarioState state)
{
_currentState = state;
}
public void UseMushrooms()
{
_currentState.UseMushrooms(this);
}
public void TakeDamage()
{
_currentState.TakeDamage(this);
}
}
public interface MarioState
{
/// <summary>
/// 吃到蘑菇
/// </summary>
/// <param name="mario"></param>
public void UseMushrooms(Mario mario);
/// <summary>
/// 收到伤害
/// </summary>
/// <param name="mario"></param>
public void TakeDamage(Mario mario);
}
public class CommonState : MarioState
{
public void TakeDamage(Mario mario)
{
mario.SetState(new DiedStata());
Console.WriteLine("[状态切换] 切换到死亡状态!!!!");
}
public void UseMushrooms(Mario mario)
{
mario.SetState(new SuperState());
Console.WriteLine("[状态切换] 切换到超级状态!!!!");
}
}
public class SuperState : MarioState
{
public void TakeDamage(Mario mario)
{
mario.SetState(new CommonState());
Console.WriteLine("[状态切换] 超级状态受伤,切换到普通状态!");
}
public void UseMushrooms(Mario mario)
{
Console.WriteLine("[提示] 已经是超级状态,无法再次使用蘑菇!");
}
}
public class DiedStata : MarioState
{
public void TakeDamage(Mario mario)
{
Console.WriteLine("[提示] 已经死亡,无法再受伤!");
}
public void UseMushrooms(Mario mario)
{
Console.WriteLine("[提示] 已经死亡,无法使用蘑菇!");
}
}
public class FireState : MarioState
{
public void TakeDamage(Mario mario)
{
mario.SetState(new CommonState());
Console.WriteLine("[状态切换] 火焰状态受伤,切换到普通状态!");
}
public void UseMushrooms(Mario mario)
{
Console.WriteLine("[提示] 火焰状态无法再次使用蘑菇!");
}
}
}
状态模式的优势
单一职责原则:将与特定状态相关的代码放在独立的类中
开闭原则:无需修改已有状态类和上下文就能引入新状态
消除条件语句:避免使用大量的条件语句来管理状态和状态转换逻辑
状态转换显式化:状态转换更加明确和可控
适用场景
对象的行为取决于它的状态,并且必须在运行时根据状态改变行为
操作中包含大量条件语句,且这些条件语句依赖于对象的状态
状态的数量较多,且状态转换逻辑复杂
状态和对应的行为需要频繁修改或扩展
实际应用案例
订单状态管理(待付款、已付款、已发货、已完成等)
工作流引擎(审批流程的不同状态)
游戏角色状态(站立、行走、奔跑、跳跃等)
电梯控制系统(上行、下行、停止、故障等)
TCP连接状态(建立连接、监听、关闭等)
状态模式通过将状态和行为绑定,使状态转换更加清晰,代码更易于维护和扩展。