状态模式
-
- [1. 什么是状态模式?](#1. 什么是状态模式?)
- [2. 状态模式的结构](#2. 状态模式的结构)
- [3. 状态模式的实现](#3. 状态模式的实现)
-
- [3.1 首先,定义状态接口:](#3.1 首先,定义状态接口:)
- [3.2 然后,实现具体的状态类:](#3.2 然后,实现具体的状态类:)
- [3.3 接下来,创建上下文类(Context):](#3.3 接下来,创建上下文类(Context):)
- [3.4 最后,客户端代码:](#3.4 最后,客户端代码:)
- [3.5 详细说明](#3.5 详细说明)
- [4. 状态模式的优点](#4. 状态模式的优点)
- [5. 状态模式的缺点](#5. 状态模式的缺点)
- [6. 状态模式的应用场景](#6. 状态模式的应用场景)
- [7. 状态模式与其他模式的关系](#7. 状态模式与其他模式的关系)
- [8. Java标准库中的状态模式](#8. Java标准库中的状态模式)
- [9. 总结](#9. 总结)
1. 什么是状态模式?
状态模式(State Pattern)是一种行为型设计模式,它允许一个对象在其内部状态改变时改变它的行为。这种模式将状态-specific(特定于状态的)行为局部化到State类中,并通过组合的方式将制定的状态类委托给Context类。
2. 状态模式的结构
状态模式主要包含以下几个角色:
- Context(环境类):维护一个对具体状态对象的引用,并定义了客户感兴趣的接口。
- State(抽象状态类):定义一个接口以封装与Context的一个特定状态相关的行为。
- ConcreteState(具体状态类):实现抽象状态类定义的接口,提供具体状态的行为。
3. 状态模式的实现
使用一个简单的电视机例子来说明状态模式。
有一个简单的电视机,它有两个状态:开机和关机。在每个状态下,电视机对相同的操作会有不同的反应。
3.1 首先,定义状态接口:
java
public interface TVState {
void pressOnOff(TV context);
void pressChannel(TV context);
void pressVolume(TV context);
}
这个接口定义了电视机可能接收到的所有操作。
3.2 然后,实现具体的状态类:
java
// 关机状态
public class OffState implements TVState {
@Override
public void pressOnOff(TV context) {
System.out.println("电视机开机了");
context.setState(new OnState());
}
@Override
public void pressChannel(TV context) {
System.out.println("无效操作,电视机没有开机");
}
@Override
public void pressVolume(TV context) {
System.out.println("无效操作,电视机没有开机");
}
}
// 开机状态
public class OnState implements TVState {
@Override
public void pressOnOff(TV context) {
System.out.println("电视机关机了");
context.setState(new OffState());
}
@Override
public void pressChannel(TV context) {
System.out.println("切换到下一个频道");
}
@Override
public void pressVolume(TV context) {
System.out.println("音量增加");
}
}
这两个类实现了 TVState
接口,分别代表电视机的开机和关机状态。
3.3 接下来,创建上下文类(Context):
java
public class TV {
private TVState state;
public TV() {
// 初始状态为关机
this.state = new OffState();
}
public void setState(TVState state) {
this.state = state;
}
public void pressOnOff() {
state.pressOnOff(this);
}
public void pressChannel() {
state.pressChannel(this);
}
public void pressVolume() {
state.pressVolume(this);
}
}
TV
类持有一个 TVState
对象,并将所有操作委托给当前状态对象。
3.4 最后,客户端代码:
java
public class Main {
public static void main(String[] args) {
TV tv = new TV();
// 电视机初始为关机状态
tv.pressChannel(); // 输出:无效操作,电视机没有开机
tv.pressVolume(); // 输出:无效操作,电视机没有开机
tv.pressOnOff(); // 输出:电视机开机了
// 现在电视机处于开机状态
tv.pressChannel(); // 输出:切换到下一个频道
tv.pressVolume(); // 输出:音量增加
tv.pressOnOff(); // 输出:电视机关机了
// 电视机又回到关机状态
tv.pressChannel(); // 输出:无效操作,电视机没有开机
}
}
3.5 详细说明
3.5.1 状态接口 (TVState):
- 定义了电视机所有可能的操作。
- 每个具体状态类都必须实现这些方法。
3.5.2 具体状态类 (OffState 和 OnState):
- 实现了状态接口。
- 每个类代表电视机的一个特定状态。
- 在每个状态下,相同的操作可能有不同的行为。
3.5.3 上下文类 (TV):
- 维护一个对当前状态对象的引用。
- 将所有与状态相关的操作委托给当前状态对象。
3.5.4 状态转换:
- 在具体状态类中处理状态转换。例如,在
OffState
的pressOnOff
方法中,状态被改变为OnState
。
3.5.5 客户端代码:
- 创建
TV
对象并调用其方法。 - 无需知道具体的状态实现,只需与
TV
对象交互。
这个例子展示了状态模式的核心思想:
- 对象的行为随着其内部状态的改变而改变。
- 状态转换被封装在具体状态类中。
- 消除了复杂的条件语句。
4. 状态模式的优点
4.1 将与特定状态相关的行为局部化,并且将不同状态的行为分割开来。
4.2 使得状态转换显式化,减少对象间的相互依赖。
4.3 消除庞大的条件分支语句。
5. 状态模式的缺点
5.1 可能会导致状态类的数量过多。
5.2 增加了系统的复杂度。
6. 状态模式的应用场景
6.1 一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为。
6.2 一个操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态。
7. 状态模式与其他模式的关系
7.1 策略模式:状态模式可以被视为策略模式的扩展。主要区别在于,在状态模式中,特定的状态知道其他状态的存在并触发从一个状态到另一个状态的转换,而策略则是独立的、互不知道的算法。
7.2 单例模式:状态对象通常被实现为单例,因为一个Context对象在任何给定时刻只能拥有一个状态。
8. Java标准库中的状态模式
8.1 java.util.Iterator
Iterator 接口是Java集合框架中最常见的状态模式实现之一。
结构:
- Context:集合对象(如 ArrayList)
- State:Iterator 接口
- Concrete States:具体的 Iterator 实现类
示例:
java
import java.util.*;
public class IteratorExample {
public static void main(String[] args) {
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
Iterator<String> iterator = list.iterator();
// hasNext() 和 next() 方法的行为取决于迭代器的当前状态
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
// 此时迭代器状态已经改变,再次调用 next() 会抛出 NoSuchElementException
try {
iterator.next();
} catch (NoSuchElementException e) {
System.out.println("Reached the end of the iterator");
}
}
}
在这个例子中,Iterator 的行为随着其内部状态(当前位置)的改变而改变。
8.2 java.io.InputStream 和其子类
InputStream 类及其子类使用了类似状态模式的设计。
结构:
- Context:InputStream 及其子类
- State:内部缓冲区的状态
- Concrete States:不同的缓冲区状态(空、部分填充、已满等)
示例:
java
import java.io.*;
public class InputStreamExample {
public static void main(String[] args) {
byte[] data = "Hello, World!".getBytes();
try (InputStream inputStream = new ByteArrayInputStream(data)) {
int byteRead;
while ((byteRead = inputStream.read()) != -1) {
System.out.print((char) byteRead);
}
// 此时流已经到达末尾,再次读取会返回 -1
System.out.println("\nEnd of stream: " + inputStream.read());
} catch (IOException e) {
e.printStackTrace();
}
}
}
在这个例子中,InputStream 的 read() 方法的行为取决于其内部状态(是否到达流的末尾)。
8.3 javax.faces.lifecycle.Lifecycle
JavaServer Faces (JSF) 技术中的 Lifecycle 类使用了状态模式。
结构:
- Context:Lifecycle 类
- State:不同的生命周期阶段
- Concrete States:各个具体的生命周期阶段实现
示例:
java
import javax.faces.lifecycle.Lifecycle;
import javax.faces.lifecycle.LifecycleFactory;
public class LifecycleExample {
public static void main(String[] args) {
LifecycleFactory lifecycleFactory = LifecycleFactory.getInstance();
Lifecycle lifecycle = lifecycleFactory.getLifecycle(LifecycleFactory.DEFAULT_LIFECYCLE);
// execute() 方法的行为取决于当前的生命周期阶段
lifecycle.execute(facesContext);
lifecycle.render(facesContext);
}
}
在 JSF 中,Lifecycle 的行为随着其所处的阶段(如 Restore View, Apply Request Values 等)而变化。
8.4 java.lang.Thread
Thread 类虽然不是严格意义上的状态模式实现,但它的设计思想与状态模式相似。
结构:
- Context:Thread 类
- State:Thread.State 枚举
- Concrete States:NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED
示例:
java
public class ThreadStateExample {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println("New thread state: " + thread.getState());
thread.start();
System.out.println("After start(): " + thread.getState());
Thread.sleep(500);
System.out.println("During sleep(): " + thread.getState());
thread.join();
System.out.println("After join(): " + thread.getState());
}
}
在这个例子中,Thread 对象的行为和可用操作随着其状态(NEW, RUNNABLE, TIMED_WAITING, TERMINATED)的变化而变化。
9. 总结
状态模式提供了一种将对象的行为封装在不同状态对象中的方法。它可以消除条件判断语句,使得对象的结构更加清晰,同时也使得状态的切换更加明确。然而,使用状态模式也可能导致状态类的数量增多,增加系统的复杂度。因此,在使用状态模式时需要权衡其利弊,根据实际情况决定是否采用。