写点什么

【愚公系列】2022 年 06 月 二十三种设计模式 (二十)- 状态模式 (State Pattern)

作者:愚公搬代码
  • 2022 年 6 月 02 日
  • 本文字数:4139 字

    阅读完需:约 14 分钟

前言

设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理的运用设计模式可以完美的解决很多问题,每种模式在现在中都有相应的原理来与之对应,每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是它能被广泛应用的原因。

一、状态模式(State Pattern)

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


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

二、使用步骤

角色

1、抽象状态(State)


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


2、具体状态(Concrete State)


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


3、环境类(Context)


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

示例


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


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!--------------------------------------------------
复制代码


<hr style=" border:solid; width:100px; height:1px;" color=#000000 size=1">

总结

优点

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

缺点

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

使用场景

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

发布于: 刚刚阅读数: 4
用户头像

还未添加个人签名 2022.03.01 加入

该博客包括:.NET、Java、前端、IOS、Android、鸿蒙、Linux、物联网、网络安全、python、大数据等相关使用及进阶知识。查看博客过程中,如有任何问题,皆可随时沟通。

评论

发布
暂无评论
【愚公系列】2022年06月 二十三种设计模式(二十)-状态模式(State Pattern)_6月月更_愚公搬代码_InfoQ写作社区