State(状态)——对象行为型模式
在面向对象环境中的状态机
1.意图
允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。
2.别名
状态对象(object for state)。
3.动机
现在我们想要自己来实现一个网络有关的程序。对于一个TCPConnection对象来说,它可能处于不同的状态:Established,Listening,Closed。当它受到其他对象的请求时,它根据状态来做出不同的反应。
这个模式关键在于用TCPState的抽象类来表示它的状态,它的子类实现特定状态下的行为。
4.适用性
·一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为。
·一个操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态。这个状态通常用一个或多个枚举常量表示。通常,有多个操作包含这一相同的条件结构。State模式将每一个条件分支放入一个独立的类中。这使得你可以根据对象自身的情况将对象的状态作为一个对象,这一对象可以不依赖于其他对象而独立变化。
5.结构
6.参与者
·Context(环境,如TCPConnection)
—定义客户感兴趣的接口。
—维护一个ConcreteState子类的实例,这个实例定义当前状态。
·State(状态,如TCPState)
—定义一个接口以封装与Context的一个特定状态相关的行为。
·ConcreteStatesubclasses(具体状态子类,如TCPEstablished、TCPListen、TCPClosed)
—每一子类实现一个与Context的一个状态相关的行为。
7.协作
·Context将与状态相关的请求委托给当前的ConcreteState对象处理。
·Context可将自身作为一个参数传递给处理该请求的状态对象。这使得状态对象在必要时可访问Context。
·Context是客户使用的主要接口。客户可用状态对象来配置一个Context,一旦一个Context配置完毕,它的客户不再需要直接与状态对象打交道。
·Context或ConcreteState子类都可决定哪个状态是另外一个的后继者,以及是在何种条件下进行状态转换。
8.效果
如果使用对象内部的一个变量来定义当前状态,那么“状态”的不同仅仅只是这个变量取值的不同,当然你可以在改变变量时改变其他的值。而且会有许多条件语句,使得代码不够清晰。
1.将与特定状态相关的行为局部化
与状态相关的代码存在于State的子类中。通过定义新的子类可以很方便地增加新的状态和转换。
2.状态的转换显式化
State模式使得状态转换有了原子性,对于Context来说,它要做的仅是重新绑定一个具体的state。
3.State对象可被共享
State 对象如果只封装了行为,不携带数据,那么它们就可以被多个 Context 对象共享同一个实例。Flyweight(享元)——对象结构型模式-CSDN博客
9.实现
1.State 子类指定状态转换
每个具体的 State 子类在处理请求(或接收事件)后,根据内部逻辑决定 Context 应该转换到哪个新的状态,然后调用 Context 提供的一个方法来请求 Context 改变其当前状态。
将状态相关的行为和转换逻辑都封装在状态类中。虽然引入了 State 子类之间的依赖,但这种依赖通常是通过 Context 提供的接口来间接进行的,或者 State 类知道如何获取状态实例(例如,如果状态是单例的话)。
2.另一种实现:利用表
这是 State 模式的一种替代实现方式,适用于状态和转换规则非常固定且规则性强的情况。
不使用类和对象的继承/多态来实现状态和行为,而是使用数据结构(如二维数组或 Map)来表示状态转换表。表的每一行代表一个当前状态,每一列代表一个输入事件,表中的值则指示在当前状态接收到该事件后应该转换到的下一个状态。可以使用 Map<CurrentStateEnum, Map<EventEnum, NextStateEnum>> transitionTable;
这样的数据结构来构建转换表。处理请求时,查找当前状态和事件对应的表项,获取下一个状态。
表驱动方式通过修改表数据就能改变转换规则,无需修改代码(但添加新的状态或事件通常仍需修改代码和表结构)。State 模式则通过添加/修改类来实现,更灵活地处理状态相关的复杂行为。
3.创建和销毁State
是需要时创建随后销毁,还是提前创建并不销毁它们。
在 Java 中,垃圾收集是自动的。策略一依赖于垃圾收集器。策略二则通过避免创建和销毁来管理生命周期,通常结合单例模式(对于无状态的 State 对象)来实现。在实际应用中,如果状态对象很小且状态切换频繁,策略二(特别是配合单例)通常是更好的选择,因为它消除了 GC 压力。如果状态对象很大或者某些状态很少用到,策略一更节省内存。
10.代码示例
// 定义状态接口或抽象类
// 使用抽象类可以提供一些默认实现,例如对无效操作抛出异常或者什么都不做
abstract class TCPState {// 所有状态共有的操作// 将 TCPConnection 实例作为参数传递,以便状态对象可以访问上下文并请求状态转换public abstract void open(TCPConnection context);public abstract void close(TCPConnection context);public abstract void send(TCPConnection context);public abstract void listen(TCPConnection context);public abstract void accept(TCPConnection context); // 添加 accept 操作,模拟接受连接// 用于打印当前状态的名称public abstract String toString();// 可以提供一个处理无效操作的默认方法protected void invalidOperation(String operation, String stateName) {System.out.println(stateName + ": 收到无效操作 '" + operation + "'.");}
}// 上下文类:TCPConnection
class TCPConnection {// 持有当前状态的引用private TCPState currentState;// 构造器:初始化为关闭状态public TCPConnection() {this.currentState = TCPClosed.instance();System.out.println("TCPConnection 已创建. 初始状态: " + currentState);}// 提供一个方法供 State 对象调用以改变当前状态void changeState(TCPState newState) {System.out.println("正在从状态 " + this.currentState + " 转换到 " + newState);this.currentState = newState;}// 客户端调用的方法,将请求委托给当前状态对象处理public void open() {currentState.open(this); // 将当前 TCPConnection 实例传递给状态对象}public void close() {currentState.close(this);}public void send() {currentState.send(this);}public void listen() {currentState.listen(this);}public void accept() {currentState.accept(this);}// 获取当前状态名称,用于演示public String getCurrentState() {return currentState.toString();}
}// 具体的 State 子类:TCPClosed (关闭状态)
// 采用单例模式,因为没有实例变量需要每个 Context 独有
class TCPClosed extends TCPState {// 单例实例private static final TCPClosed instance = new TCPClosed();private TCPClosed() {}public static TCPClosed instance() {return instance;}// 实现状态特有的行为@Overridepublic void open(TCPConnection context) {System.out.println("TCPClosed: 收到 Open. 正在转换为 Listen 状态.");// 在这里执行打开连接// 请求上下文改变状态context.changeState(TCPListen.instance());}@Overridepublic void close(TCPConnection context) {System.out.println("TCPClosed: 收到 Close. 当前已是关闭状态.");// 保持在关闭状态}@Overridepublic void send(TCPConnection context) {invalidOperation("Send", toString());// 保持在关闭状态}@Overridepublic void listen(TCPConnection context) {System.out.println("TCPClosed: 收到 Listen. 正在转换为 Listen 状态.");// 在这里执行监听所需的逻辑(简化)context.changeState(TCPListen.instance());}@Overridepublic void accept(TCPConnection context) {invalidOperation("Accept", toString());// 保持在关闭状态}@Overridepublic String toString() {return "Closed";}
}// 具体的 State 子类:TCPListen (监听状态)
class TCPListen extends TCPState {private static final TCPListen instance = new TCPListen();private TCPListen() {}public static TCPListen instance() {return instance;}@Overridepublic void open(TCPConnection context) {invalidOperation("Open", toString());// 保持在监听状态}@Overridepublic void close(TCPConnection context) {System.out.println("TCPListen: 收到 Close. 正在转换为 Closed 状态.");// 执行关闭监听所需的逻辑(简化)context.changeState(TCPClosed.instance());}@Overridepublic void send(TCPConnection context) {invalidOperation("Send", toString());// 保持在监听状态}@Overridepublic void listen(TCPConnection context) {System.out.println("TCPListen: 收到 Listen. 当前已是监听状态.");// 保持在监听状态}@Overridepublic void accept(TCPConnection context) {System.out.println("TCPListen: 收到 Accept. 正在转换为 Established 状态.");// 执行接受连接所需的逻辑(简化)// 假设接受连接后进入 Established 状态context.changeState(TCPEstablished.instance());}@Overridepublic String toString() {return "Listen";}
}// 具体的 State 子类:TCPEstablished (已建立连接状态)
class TCPEstablished extends TCPState {private static final TCPEstablished instance = new TCPEstablished();private TCPEstablished() {}public static TCPEstablished instance() {return instance;}@Overridepublic void open(TCPConnection context) {invalidOperation("Open", toString());// 保持在 Established 状态}@Overridepublic void close(TCPConnection context) {System.out.println("TCPEstablished: 收到 Close. 正在转换为 Closed 状态.");// 执行关闭连接所需的逻辑(简化)context.changeState(TCPClosed.instance());}@Overridepublic void send(TCPConnection context) {System.out.println("TCPEstablished: 收到 Send. 正在发送数据...");// 执行发送数据逻辑(简化)// 发送数据通常不会改变状态// 保持在 Established 状态}@Overridepublic void listen(TCPConnection context) {invalidOperation("Listen", toString());// 保持在 Established 状态}@Overridepublic void accept(TCPConnection context) {invalidOperation("Accept", toString());// 保持在 Established 状态}@Overridepublic String toString() {return "Established";}
}// 客户端代码示例
public class TCPExample {public static void main(String[] args) {// 创建一个 TCP 连接,初始状态为 ClosedTCPConnection connection = new TCPConnection();System.out.println("\n当前状态: " + connection.getCurrentState());// 模拟客户端操作connection.open(); // 应该从 Closed 转换到 ListenSystem.out.println("当前状态: " + connection.getCurrentState());connection.send(); // 在 Listen 状态下 Send 是无效操作System.out.println("当前状态: " + connection.getCurrentState());connection.listen(); // 在 Listen 状态下 Listen 保持原状态System.out.println("当前状态: " + connection.getCurrentState());// 模拟接受一个连接请求,导致状态从 Listen 转换到 Establishedconnection.accept();System.out.println("当前状态: " + connection.getCurrentState());connection.send(); // 在 Established 状态下 Send 是有效操作System.out.println("当前状态: " + connection.getCurrentState());connection.listen(); // 在 Established 状态下 Listen 是无效操作System.out.println("当前状态: " + connection.getCurrentState());connection.close(); // 应该从 Established 转换到 ClosedSystem.out.println("当前状态: " + connection.getCurrentState());connection.send(); // 在 Closed 状态下 Send 是无效操作System.out.println("当前状态: " + connection.getCurrentState());connection.open(); // 再次打开,从 Closed 到 ListenSystem.out.println("当前状态: " + connection.getCurrentState());}
}
TCPConnection
对象创建时,初始化其 currentState
为某个初始状态的单例实例(如 TCPClosed.instance()
)。
当客户端调用 TCPConnection
的某个方法(如 connection.open()
)时,TCPConnection
不会直接执行状态逻辑,而是委托给当前的 currentState
对象,即调用 currentState.open(this)
,并将自身作为参数传递过去。
当前的 State 对象(例如 TCPClosed
的实例)接收到委托的调用后,执行该状态下对应该操作的具体逻辑(例如,打印一条消息)。
如果该操作导致状态转换,当前 State 对象会调用 TCPConnection
提供的 changeState()
方法,将 TCPConnection
的 currentState
引用设置为下一个状态的单例实例(例如,调用 context.changeState(TCPListen.instance())
)。
11.相关模式
Flyweight解释了何时以及怎样共享状态对象。
状态对象通常是Singleton。