设计模式——状态模式

145 阅读4分钟

一:介绍

定义:当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类
简单的解释一下状态模式的定义就是当一个对象的状态改变了,这个对象的行为也会跟着改变,而看起来就像是这个类改变了一样


二:适用场景

在写程序的过程中,经常会遇到多种状态的判断,首先想到的就是通常的if-else if写法,但是这种写法会导致方法过长,使当前类的责任过大而违背了单一职责的原则,并且后期不好维护,状态模式主要解决的就是这种状态的逻辑判断过于复杂的情况
我们可以把很多状态的共同相关行为都放入一个抽象类或接口中管理,这样定义不同的状态类去继承基类并实现方法就可以增加修改不同状态,再通过一个统一的管理类去管理当前状态的行为,这样做就可以消除繁琐的条件分支语句,减少类与类相互间的依赖
总的来说,状态模式有三种角色,抽象状态、具体状态和状态管理。抽象状态负责定义很多状态的共同相关行为,具体状态角色只负责处理本状态的任务和过渡到其他状态两个职责,状态管理负责状态的过渡,这样将过渡与状态分离,提高了程序可扩展性和代码的可阅读性


三:状态模式思想(官方案例)

——Context类:可以看作是一个Manager去管理当前状态以及当前状态的行为,它持有一个状态的对象去负责状态的切换
——IState接口:封装每个状态类共同的行为,这些行为的使用(调用)是在Context类中
——具体状态类:StateA与StateB是两个不同但有关联的状态

using System;

public class MainClass
{
    public static void Main()
    {
        Context context = new Context(new StateA());
        context.Request();
        context.Request();
        context.Request();
        context.Request();
    }
}

/// <summary>
/// 定义一个接口类去封装每个状态的相关行为(每个状态去继承此接口)
/// </summary>
public interface IState
{
    void Handle(Context context);
}

/// <summary>
/// 管理所有状态的总管理类
/// </summary>
public class Context
{
    //当前状态
    private IState state;
    public IState State
    {
        get
        {
            return state;
        }
        set
        {
            state = value;
        }
    }

    //构造函数初始化
    public Context(IState _state)
    {
        state = _state;
    }

    //请求下一个操作
    public void Request()
    {
        state.Handle(this);
    }
}

public class StateA : IState
{
    public void Handle(Context context)
    {
        Console.WriteLine("StateA");
        context.State = new StateB();
    }
}

public class StateB : IState
{
    public void Handle(Context context)
    {
        Console.WriteLine("StateB");
        context.State = new StateA();
    }
}

四:使用状态模式的思想处理问题

例如一个游戏中人物有三种状态:站立、走路、跑步

不使用状态模式的写法如下:

using System;

/// <summary>
/// 人物状态
/// </summary>
public enum PlayerState
{
    Idle,
    Walk,
    Run,
}

public class MainClass
{
    public static void Main()
    {
        PlayerState state = PlayerState.Idle;
        if (state == PlayerState.Idle)
        {
            Console.WriteLine("站立");
        }
        else if (state == PlayerState.Walk)
        {
            Console.WriteLine("走路");
        }
        else if (state == PlayerState.Run)
        {
            Console.WriteLine("跑步");
        }
    }
}

上面的写法虽然没什么问题,但是有几点明显的缺点:
——假设有100个状态则需要100个if-else if或case,方法过长是不好的
——假设我们只需要修改在跑步状态时的Debug输出,则相当于修改了这个类,违背了单一职责原则也就是开放-封闭原则,不利于对项目的拓展和复用
对上面的写法使用状态模式进行改写:

using System;

/// <summary>
/// 人物状态
/// </summary>
public enum PlayerState
{
    Idle,
    Walk,
    Run,
}

public class MainClass
{
    public static void Main()
    {
        StateManager playerState = new StateManager(new Idle());
        playerState.Handle();

        playerState.SetState(new Run());
        playerState.Handle();
    }
}

public interface IPlayerState
{
    void Handle();
}

public class StateManager
{
    public IPlayerState State { get; set; }

    public StateManager(IPlayerState _state)
    {
        State = _state;
    }

    public void Handle()
    {
        State.Handle();
    }

    public void SetState(IPlayerState _state)
    {
        State = _state;
    }
}

public class Idle : IPlayerState
{
    public void Handle()
    {
        Console.WriteLine("站立");
    }
}

public class Walk : IPlayerState
{
    public void Handle()
    {
        Console.WriteLine("走路");
    }
}

public class Run : IPlayerState
{
    public void Handle()
    {
        Console.WriteLine("跑步");
    }
}

使用状态模式改写后,有几个明显的优点:
——去掉了if else-if,使代码优雅了很多,假设现在需要修改在跑步状态时的Debug输出,则只要改动Run类即可,不需要改动其他的类
——使用多态StateManager playerState = new StateManager(new Idle())这种方式代替了条件判断,使代码的扩展性更强,假如现在需要增加一个跳的状态,则只需要创建一个Jump类即可


五:现实生活中的关灯开灯问题

例如一个开关,按下后开启微弱灯光,再按一下开启强烈灯光,再按一下关闭灯

using System;

public class MainClass
{
    public static void Main()
    {
        StateManager state = new StateManager(new OffLightState());
        state.OnPress();
        state.OnPress();
        state.OnPress();
        state.OnPress();
    }
}

public interface IState
{
    void OnPress(StateManager manager);
}

public class StateManager
{
    public IState State { get; set; }

    public StateManager(IState _state)
    {
        State = _state;
    }

    public void OnPress()
    {
        State.OnPress(this);
        Console.WriteLine(State);
    }

    public void SetState(IState _state)
    {
        State = _state;
    }
}

public class WeakLightState : IState
{
    public void OnPress(StateManager manager)
    {
        manager.SetState(new StrongLightState());
    }
}

public class StrongLightState : IState
{
    public void OnPress(StateManager manager)
    {
        manager.SetState(new OffLightState());
    }
}

public class OffLightState : IState
{
    public void OnPress(StateManager manager)
    {
        manager.SetState(new WeakLightState());
    }
}