状态模式

状态模式是一种行为设计模式,它允许对象在其内部状态改变时改变其行为,使对象看起来像是修改了它的类。

状态模式是状态机的一种实现方式即可。状态机又叫有限状态机,它有 3 个部分组成:状态、事件、动作。其中,事件也称为转移条件。事件触发状态的转移及动作的执行。不过,动作不是必须的,也可能只转移状态,不执行任何动作。

核心概念

状态模式将特定状态相关的行为封装在独立的类中,并将请求委托给当前状态对象。当对象的内部状态改变时,它会改变自己的行为,而不需要修改原有类。

主要组件

  1. ​Context(上下文)​​:

    • 维护一个具体状态对象的实例

    • 定义客户端感兴趣的接口

    • 将状态相关的操作委托给当前状态对象

  2. ​State(状态)​​:

    • 定义一个接口,封装与Context的一个特定状态相关的行为

  3. ​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("[提示] 火焰状态无法再次使用蘑菇!");
        }
    }
}

状态模式的优势

  1. ​单一职责原则​​:将与特定状态相关的代码放在独立的类中

  2. ​开闭原则​​:无需修改已有状态类和上下文就能引入新状态

  3. ​消除条件语句​​:避免使用大量的条件语句来管理状态和状态转换逻辑

  4. ​状态转换显式化​​:状态转换更加明确和可控

适用场景

  1. 对象的行为取决于它的状态,并且必须在运行时根据状态改变行为

  2. 操作中包含大量条件语句,且这些条件语句依赖于对象的状态

  3. 状态的数量较多,且状态转换逻辑复杂

  4. 状态和对应的行为需要频繁修改或扩展

实际应用案例

  1. 订单状态管理(待付款、已付款、已发货、已完成等)

  2. 工作流引擎(审批流程的不同状态)

  3. 游戏角色状态(站立、行走、奔跑、跳跃等)

  4. 电梯控制系统(上行、下行、停止、故障等)

  5. TCP连接状态(建立连接、监听、关闭等)

状态模式通过将状态和行为绑定,使状态转换更加清晰,代码更易于维护和扩展。