21、C#设计模式 - 状态模式

状态模式(State Pattern)

状态模式属于行为型模式,它允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。

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

角色:

1、 抽象状态(State);

状态模式的核心基类,它表示某种对象的不同状态;

2、 具体状态(ConcreteState);

实现抽象状态的具体状态类;

3、 环境类(Context);

拥有状态的具体对象,该对象会根据内部不同的对象有不同的行为。

示例:

 

命名空间StatePattern中包含抽象状态类State,代表水的3种状态,0度及以下时为SolidState固体状态,0到100度为LiquidState液体状态,100度及以上时为GasState气体状态,并且不同的状态可以改变水类Water中喝水Drink方法的行为。本案例尝试以水的3种不同状态来向大家阐述状态模式在实际开发中的应用。

namespace StatePattern
public class Water {

    public State State { get; set; }

    public double Temperature { get; set; } = 0;

    public Water() {
        State = new SolidState();
        State.Water = this;
    }

    public Water Increase(int value) {
        State.Increase(value);
        return this;
    }

    public Water Reduce(int value) {
        State.Reduce(value);
        return this;
    }

    public Water Drink() {
        if (this.State is LiquidState) {
            Console.WriteLine("You can drink!");
        }
        else {
            Console.WriteLine("You can not drink!");
        }
        Console.WriteLine(Const.LINE_BREAK);
        return this;
    }

}

Water水类充当环境类,公开一个状态基类,并在内部维护一个温度。Increase调用State的升温,而Reduce调用State的降温,最后的Drink方法会因为水的状态的不同而拥有不同的行为。为了简化逻辑,在本例中,只有当水为液体时才能被饮用。

public abstract partial class State {

    public static Water Water { get; set; }

    protected static string StateName { private get; set; }

    public void Increase(int value) {
        if (value == 0) return;
        if (value < 0) throw new ArgumentException();
        OnStateChanging();
        Water.Temperature += value;
        ChangeState();
    }

    public void Reduce(int value) {
        if (value == 0) return;
        if (value < 0) throw new ArgumentException();
        if (Water.Temperature - value <= Const.ABSOLUTE_ZERO) {
            throw new UnReachableException();
        }
        OnStateChanging();
        Water.Temperature -= value;
        ChangeState();
    }

}

抽象状态基类,首先公开一个水的引用,并在所有实现类中共享StateName状态名,Increase为水升高一个温度,而Reduce为水降温。

public abstract partial class State {

    private void ChangeState() {
        if (Water.Temperature <= 0) {
            Water.State = new SolidState();
        }
        else if (Water.Temperature > 0 && Water.Temperature < 100) {
            Water.State = new LiquidState();
        }
        else {
            Water.State = new GasState();
        }
        OnStateChanged();
    }

    protected virtual void OnStateChanging() {
        Console.WriteLine(Const.ON_STATE_CHANGING);
        Console.WriteLine(
            string.Format(Const.TEMPERATURE_INFO,
                            Water.Temperature, StateName));
    }

    protected virtual void OnStateChanged() {
        Console.WriteLine(Const.ON_STATE_CHANGED);
        Console.WriteLine(
            string.Format(Const.TEMPERATURE_INFO,
                            Water.Temperature, StateName));
        Console.WriteLine(Const.LINE_BREAK);
    }

}

抽象状态基类的第2部分(partial ),定义ChangeState方法以在改变温度时更改状态,另外定义OnStateChanging和OnStateChanged这2个受保护的虚方法以便提供“子类可以决定是否重写相应的方法来影响父类”的这样一个功能(OOP特性)。

public class SolidState : State {

    public SolidState() {
        StateName = "Solid";
    }

}
public class LiquidState : State {

    public LiquidState() {
        StateName = "Liquid";
    }

}
public class GasState : State {

    public GasState() {
        StateName = "Gas";
    }

}

水的3种状态的具体实现类,SolidState固体状态、LiquidState液体状态和GasState气体状态,由于我们在状态基类中封装了较多的功能,所以此处的3个具体类都比较精简,只在构造函数中更改共享的StateName状态名称字段。在实际开发过程中,应当尽可能的将具体的功能封装在状态实现类中。

public class Const {

    public const double ABSOLUTE_ZERO = -273.15;

    public const string LINE_BREAK =
        "--------------------------------------------------";

    public const string ON_STATE_CHANGING = "OnStateChanging()";

    public const string ON_STATE_CHANGED = "OnStateChanged()";

    public const string TEMPERATURE_INFO = "The temperature is {0} °C" +
        " and state name is {1}!";

}

常量类,维护一些在本案例中经常使用到的字符串或数值。在实际开发过程中不应当有此类,应该将相应的常量放在具体要使用的类中。2017年,阿里发布《阿里巴巴Java开发手册》,其中有一节提到此准则,所有使用面向对象编程语言的开发人员都应当遵从。

public class UnReachableException : Exception {

    public UnReachableException()
        : base("Absolute zero cannot be reached!") {

    }

    public UnReachableException(string message, Exception innerException)
        : base(message, innerException) {

    }

}

绝对零度无法到达异常类UnReachableException,进行简单的异常处理。

public class Program {

    private static Water _water = new Water();

    public static void Main(string[] args) {
        try {
            _water.Increase(68)
                  .Drink()
                  .Increase(82)
                  .Drink()
                  .Reduce(90)
                  .Drink()
                  .Reduce(0)
                  .Reduce(80)
                  .Drink()
                  .Reduce(300)
                  .Drink();
        }
        catch (Exception ex) {
            Console.WriteLine(ex.Message);
            Console.WriteLine(Const.LINE_BREAK);
        }

        Console.ReadKey();
    }

}

以上是本案例的调用方代码,升温方法Increase、降温方法Reduce和喝水方法Drink经过特别的处理以支持方法链。以下是这个案例的输出结果:

OnStateChanging()
The temperature is 0 °C and state name is Solid!
OnStateChanged()
The temperature is 68 °C and state name is Liquid!
--------------------------------------------------
You can drink!
--------------------------------------------------
OnStateChanging()
The temperature is 68 °C and state name is Liquid!
OnStateChanged()
The temperature is 150 °C and state name is Gas!
--------------------------------------------------
You can not drink!
--------------------------------------------------
OnStateChanging()
The temperature is 150 °C and state name is Gas!
OnStateChanged()
The temperature is 60 °C and state name is Liquid!
--------------------------------------------------
You can drink!
--------------------------------------------------
OnStateChanging()
The temperature is 60 °C and state name is Liquid!
OnStateChanged()
The temperature is -20 °C and state name is Solid!
--------------------------------------------------
You can not drink!
--------------------------------------------------
Absolute zero cannot be reached!
--------------------------------------------------

优点:

1、 封装了转换规则;
2、 枚举可能的状态,在枚举状态之前需要确定状态种类;
3、 将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为;
4、 允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块;
5、 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数;

缺点:

1、 状态模式的使用必然会增加系统类和对象的个数;
2、 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱;
3、 状态模式对"开闭原则"的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码;

使用场景:

1、 行为随状态改变而改变的场景;
2、 条件、分支语句的代替者;