本篇讲解Java设计模式中的状态模式,分为定义、模式应用前案例、结构、模式应用后案例、适用场景、模式可能存在的困惑和本质探讨7个部分。
定义
状态模式允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它所属的类。
在新的分类方式中,状态模式被划分至类属性和行为联动相关需求类别中,其应对的是原有类中多种状态及导致的不同行为的情形。
模式应用前案例
从定义可以看出,状态模式隐含了一个对象可以有多种状态,并且每种状态应该有自身的行为。
学过计算机的同学应该对TCP连接比较熟悉,互联网的底层通信协议主要就是基于TCP。大家应该也对TCP的三次握手有所耳闻。下面,我们就以TCP连接为例,看看未使用状态模式之前的代码实现。
public class TCPConnection {//TCP连接类
private String state;
public void setState(String state) {
this.state = state;
}
public void listen(){
if ("LISTEN".equals(this.state)) {// 只有当处于监听状态时才能接受连接
System.out.println("TCP connection is now accepting incoming connections");
}else {
System.out.println("TCP connection is not in listening mode, unable to accept incoming connections");
}
}
public void open() {
if ("ESTABLISHED".equals(this.state)) {
System.out.println("TCP connection is already established");
} else if ("LISTEN".equals(this.state)) {
System.out.println("TCP connection is in listening mode");
// 监听连接请求的逻辑...
} else if ("CLOSED".equals(this.state)) {
System.out.println("TCP connection has been closed");
}
}
public void close() {
if ("ESTABLISHED".equals(this.state)) {
System.out.println("Closing the established TCP connection");
// 关闭已建立连接的逻辑...
} else if ("LISTEN".equals(this.state)) {
System.out.println("Closing the listening mode of TCP connection");
// 关闭监听模式的逻辑...
} else if ("CLOSED".equals(this.state)){
System.out.println("The TCP Connection is already closed");
}
}
}
public class Client {//调用方代码
public static void main(String[] args) {
TCPConnection tcpConnection = new TCPConnection();
// 初始状态为 LISTEN
tcpConnection.setState("LISTEN");
// 执行监听操作
tcpConnection.listen();
// 改变状态为 ESTABLISHED
tcpConnection.setState("ESTABLISHED");
// 执行打开连接操作
tcpConnection.open();
// 改变状态为 CLOSED
tcpConnection.setState("CLOSED");
//执行关闭连接操作
tcpConnection.close();
}
}
上述代码主要存在两大问题。一是TCPConnection类中包含了各种状态及不同行为的代码,如果后续还需要增加或删除状态,不满足OCP开闭原则。
二是Client类与TCPConnection之间进行状态信息交互时,还需要知晓具体状态的名称以及状态对应的方法名称,对调用方不友好。
结构
状态模式的示例代码实现如下。
public class Context {
private State state;
// 初始化 Context 时设置初始状态
public Context(State state) {
this.state = state;
}
// 设置当前状态
public void setState(State state) {
this.state = state;
}
// 请求处理,委托给当前状态对象
public void request() {
state.handle(this);
}
}
public interface State {
void handle(Context context);
}
public class ConcreteStateA implements State {
@Override
public void handle(Context context) {
System.out.println("ConcreteStateA handling the request.");
// 在某种条件下,切换到 ConcreteStateB
context.setState(new ConcreteStateB());
}
}
public class ConcreteStateB implements State {
@Override
public void handle(Context context) {
System.out.println("ConcreteStateB handling the request.");
// 在某种条件下,切换到 ConcreteStateC
context.setState(new ConcreteStateC());
}
}
public class ConcreteStateC implements State {
@Override
public void handle(Context context) {
System.out.println("ConcreteStateC handling the request.");
// 在某种条件下,可以切换回 ConcreteStateA 或其他状态
// context.setState(new ConcreteStateA());
}
}
public class Client {
public static void main(String[] args) {
// 创建 Context 对象,并设置初始状态为 ConcreteStateA
Context context = new Context(new ConcreteStateA());
// 执行请求,状态对象会根据内部逻辑处理请求,并可能切换状态
context.request(); // 输出 "ConcreteStateA handling the request."
// 再次执行请求,由于状态已切换为 ConcreteStateB,因此行为也会改变
context.request(); // 输出 "ConcreteStateB handling the request."
// 再次执行请求,由于状态已切换为 ConcreteStateC,因此行为也会改变
context.request(); // 输出 "ConcreteStateC handling the request."
}
}
从状态模式的结构和示例代码中,状态使用一个家族类来实现。Context核心类与状态家族类的抽象或接口关联,这样后续增加或删除状态都不需要改变Context核心类。
此外,通过状态家族类这种实现方式,可以将不同状态对外的行为都进行统一,对于调用方更加友好。
模式应用后案例
上面TCP连接状态的案例,在使用状态模式后的代码实现如下:
首先,状态抽象成一个家族类实现,包括一个状态接口和三个状态的实现。
public interface ITCPState {//TCP状态接口
abstract void handle(TCPConnection connection);
}
public class TCPListenState implements ITCPState {//TCPListen状态类
@Override
public void handle(TCPConnection connection) {
System.out.println( "TP Connection is now accepting incoming connections");
String clientAddress = "192.168.0.1";
int clientPort = 12345;
System.out.println("Incoming connection request from: "+clientAddress +":"+clientPort);
}
}
public class TCPEstablishedState implements ITCPState {//TCPEstablished状态类
@Override
public void handle(TCPConnection connection) {
System.out.println("TCP connection is already established");
}
}
public class TCPClosedState implements ITCPState {//TCPClosed状态类
@Override
public void handle(TCPConnection connection) {
System.out.println("The Connection is already closed");
}
}
原来TCPConnection大杂烩类简化如下,其中组合了状态家族类中的顶层接口,代码实现如下。
public class TCPConnection {//Context上下文类
private ITCPState state;
public TCPConnection(ITCPState state) {
this.state = state;
}
public void setState(ITCPState state) {
this.state = state;
}
public void request() {
this.state.handle(this);
}
}
最后,调用方代码实现如下。
public class Client {//调用方代码
public static void main(String[] args) {
// 创建TCPConnection对象并设置初始状态为Closed
TCPConnection tcpConnection = new TCPConnection(new TCPClosedState());
// 变化状态为 LISTEN
tcpConnection.setState(new TCPListenState());
// 执行监听操作
tcpConnection.request();
// 改变状态为 ESTABLISHED
tcpConnection.setState(new TCPEstablishedState());
// 执行打开连接操作
tcpConnection.request();
// 改变状态为 CLOSED
tcpConnection.setState(new TCPClosedState());
//执行关闭连接操作
tcpConnection.request();
}
}
相比原有的实现代码,现在TCPConnection类不会再因为状态的增加、删除而需要一并变更。
其次,Client类不再需要记住交互的细节信息,并且可以通过统一的接口的进行交互。
适用场景
当一个对象在生命周期中会产生多种状态,并且不同的状态下会产生相应的行为时,就应该考虑使用状态模式。
模式可能存在的困惑
困惑1: Context意思上是上下文类,为什么设计模式中会取这样一个名称?
在23个设计模式中,只有解释器模式、策略模式(后面讲到)和状态模式中有Context类。在三个设计模式中,未使用设计模式之前,Context类都是一个大杂烩类,既包括状态也包括不同的行为。
在使用设计模式之后,发现部分或全部核心的行为逻辑都被挪出去,而原来Context类中主要剩下了状态信息,并且这些状态信息成为行为发挥作用时的上下文信息。
困惑2:状态模式与解释器模式的结构非常类似,两者之间有什么区别?
结构上确实很类似,但是细节上有不同。解释器模式的核心是一种状态可以对应多种行为,而状态模式的核心是不同的状态对应不同的行为。
本质
在面向对象程序中,可以认为类是由状态+行为构成的。状态和行为之间可能有不同的关系,比如不同的状态有相同的行为、不同的状态有不同的行为、相同的状态有不同的行为等。状态模式的本质就是提供了一种处理不同状态有不同行为的机制。
本文暂时没有评论,来添加一个吧(●'◡'●)