5、状态模式
状态模式
状态的定义
应用程序中的有些对象可能会根据不同的情况做出不同的行为,我们把这种对象称为有状态的对象,而把影响对象行为的一个或多个动态变化的属性称为状态。当有状态的对象与外部事件产生互动时,其内部状态会发生改变,从而使得其行为也随之发生改变。
状态(State)模式的定义:对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。
状态模式的解决思想是:当控制一个对象状态转换的条件表达式过于复杂时,把相关“判断逻辑”提取出来,放到一系列的状态类当中,这样可以把原来复杂的逻辑判断简单化。
状态模式实现
状态模式把受环境改变的对象行为包装在不同的状态对象里,其意图是让一个对象在其内部状态改变的时候,其行为也随之改变。现在我们来分析其基本结构和实现方法。
状态模式包含以下主要角色:
- 环境(Context)角色:也称为上下文,它定义了客户感兴趣的接口,维护一个当前状态,并将与状态相关的操作委托给当前状态对象来处理。
- 抽象状态(State)角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为。
- 具体状态(Concrete State)角色:实现抽象状态所对应的行为。
优缺点
优点
- 状态模式将与特定状态相关的行为局部化到一个状态中,并且将不同状态的行为分割开来,满足“单一职责原则”。
- 减少对象间的相互依赖。将不同的状态引入独立的对象中会使得状态转换变得更加明确,且减少对象间的相互依赖。
- 有利于程序的扩展。通过定义新的子类很容易地增加新的状态和转换。
- 可以将 各种状态 的 转换逻辑 , 分布到 状态 的子类中 , 减少相互依赖 ;
- 状态模式 与 享元模式 , 可以配合在一起使用 , 可以使用享元模式 , 在多个上下文中 , 共享状态实例 ;
- 减少条件语句:通过使用状态模式,可以避免在代码中出现大量的条件判断语句(如switch-case或if-else),从而提高代码的可读性和简洁性。
- 符合开闭原则:状态模式允许在不修改现有代码的情况下添加新的状态,符合开闭原则,即对扩展开放,对修改关闭。
- 封装性好:状态的切换和行为变化被封装在类的内部实现,外部调用无需了解类内部如何实现状态和行为的变换。
- 易于扩展:增加新的状态只需增加新的状态类,而不需要修改现有代码,这使得系统具有良好的可维护性和可扩展性。
- 提高灵活性:状态模式支持动态地添加新的状态,无需修改现有代码,增强了系统的灵活性和可扩展性。
缺点
- 状态模式的使用必然会增加系统的类与对象的个数。
- 状态模式的结构与实现都较为复杂,如果使用不当会导致程序结构和代码的混乱。
- 如果 状态数量 比较多 , 状态类 的 数量会增加 , 业务场景系统变得很复杂 ; 如果业务中某个对象由几十上百个状态 , 就会很复杂 , 这时就需要对状态进行拆分处理 ;
状态模式应用场景
- 对象的行为依赖于其状态:当一个对象的行为随着其内部状态的变化而变化时,可以使用状态模式。例如,在电子商务系统中,订单可能经历多种状态(如创建、支付、发货、完成和取消),每个状态下可以执行的操作不同,状态模式可以很好地管理这种状态的变化。
- 代码中包含大量的条件语句:当代码中包含大量的条件语句,并且这些条件语句表示对象的不同状态时,可以使用状态模式。状态模式可以将不同状态的处理分离出来,使得代码更加清晰。
- 对象的状态转换规则非常复杂时,可以使用状态模式。状态模式可以将状态转换规则封装在状态类中,使得状态转换更加灵活、可扩展。
- 需要增加新的状态:当需要增加新的状态时,可以使用状态模式。通过增加新的状态类,可以很容易地扩展状态模式。
- 对象具有多种状态且状态之间存在转换关系:当对象的行为随着其内部状态的变化而变化,并且这些状态之间存在复杂的转换逻辑时,状态模式非常有用。例如,在游戏编程中,可以根据游戏的不同阶段(如游戏开始、进行中、结束)来改变游戏逻辑。
- 并发编程中的线程状态管理:在并发编程中,可以根据线程的不同状态(如运行、等待、阻塞)来改变线程的行为。
状态模式简单实现
定义抽象状态
public abstract class State {
/*操作状态的方法*/
public abstract void Handle(Context context);
}
定义操作状态上下文
public class Context {
/*定义状态*/
private State state;
public Context(){
state = new ConcreteStateA();
}
/*在Context中可以封装对状态的操作*/
/*设置新状态*/
public void setState(State state){
this.state = state;
}
/*读取状态*/
public State getState()
{
return(state);
}
/*处理状态*/
public void Handle()
{
state.Handle(this);
}
}
具体状态A
public class ConcreteStateA extends State{
public void Handle(Context context)
{
System.out.println("当前状态是 A.");
/*重新设置新状态 触发将状态A转换为状态B*/
context.setState(new ConcreteStateB());
}
}
具体状态B
public class ConcreteStateB extends State{
public void Handle(Context context)
{
System.out.println("当前状态是 B.");
/*设置新状态B*/
context.setState(new ConcreteStateA());
}
}
Client
public class State_test_API {
public static void main(String[] args) {
/*创建状态上下文*/
Context context = new Context();
/*处理状态,调用接口中的方法*/
context.Handle();
}
}
状态模式实现线程状态转换
设计
多线程存在 5 种状态,分别为新建状态、就绪状态、运行状态、阻塞状态和死亡状态,各个状态当遇到相关方法调用或事件触发时会转换到其他状态,其状态转换规律如图所示:
现在先定义一个抽象状态类(TheadState),然后为上图所示的每个状态设计一个具体状态类,它们是新建状态(New)、就绪状态(Runnable )、运行状态(Running)、阻塞状态(Blocked)和死亡状态(Dead),每个状态中有触发它们转变状态的方法,环境类(ThreadContext)中先生成一个初始状态(New),并提供相关触发方法,线程状态转换程序的结构图如下图所示:
定义抽象状态
/*封装线程状态*/
public abstract class ThreadState {
protected String stateName; //状态名
}
抽象状态,封装状态锁对应的行为,当状态发生改变时,触发此行为调用;
环境上下文
public class ThreadContext {
/*封装线程状态上下文 服务对状态进行操作*/
/*保存线程状态对象*/
private ThreadState state;
public ThreadContext() {
state=new New();
}
/*下面封装对线程状态操作的方法*/
/*设置新状态*/
public void setState(ThreadState state) {
this.state=state;
}
/*获取当前状态*/
public ThreadState getState() {
return state;
}
/*启动一个新线程*/
public void start() {
((New) state).start(this);
}
public void getCPU() {
((Runnable) state).getCPU(this);
}
/*挂起线程*/
public void suspend() {
((Running) state).suspend(this);
}
/*暂停线程*/
public void stop() {
((Running) state).stop(this);
}
public void resume() {
((Blocked) state).resume(this);
}
}
新建线程
public class New extends ThreadState{
public New() {
stateName="新建状态";
System.out.println("当前线程处于:新建状态.");
}
/*启动新线程*/
public void start(ThreadContext hj) {
System.out.print("调用start()方法-->");
if(stateName.equals("新建状态")) {
/*将新建状态的线程转换为可运行状态*/
hj.setState(new Runnable());
} else {
System.out.println("当前线程不是新建状态,不能调用start()方法.");
}
}
}
可运行状态
public class Runnable extends ThreadState{
public Runnable() {
stateName="就绪状态";
System.out.println("当前线程处于:就绪状态.");
}
public void getCPU(ThreadContext hj) {
System.out.print("获得CPU时间-->");
if(stateName.equals("就绪状态")) {
/*将可运行状态线程转换为运行状态*/
hj.setState(new Running());
} else {
System.out.println("当前线程不是就绪状态,不能获取CPU.");
}
}
}
运行态线程
public class Running extends ThreadState{
public Running() {
stateName="运行状态";
System.out.println("当前线程处于:运行状态.");
}
/*挂起线程*/
public void suspend(ThreadContext hj) {
System.out.print("调用suspend()方法-->");
if(stateName.equals("运行状态")) {
/*运行状态的线程转换为阻塞状态*/
hj.setState(new Blocked());
} else {
System.out.println("当前线程不是运行状态,不能调用suspend()方法.");
}
}
/*暂停线程*/
public void stop(ThreadContext hj) {
System.out.print("调用stop()方法-->");
if(stateName.equals("运行状态")) {
/*运行状态的线程转换为死亡状态*/
hj.setState(new Dead());
} else {
System.out.println("当前线程不是运行状态,不能调用stop()方法.");
}
}
}
阻塞线程
public class Blocked extends ThreadState {
public Blocked() {
stateName="阻塞状态";
System.out.println("当前线程处于:阻塞状态.");
}
public void resume(ThreadContext hj) {
System.out.print("调用resume()方法-->");
if(stateName.equals("阻塞状态")) {
/*阻塞状态线程转换为可运行状态*/
hj.setState(new Runnable());
} else {
System.out.println("当前线程不是阻塞状态,不能调用resume()方法.");
}
}
}
死亡线程
public class Dead extends ThreadState{
public Dead() {
stateName="死亡状态";
System.out.println("当前线程处于:死亡状态.");
}
}
客户端
public class ThreadState_test_API {
public static void main(String[] args) {
/*创建状态上下文*/
ThreadContext context = new ThreadContext();
context.start();
context.getCPU();
context.suspend();
context.resume();
context.getCPU();
context.stop();
}
}
上述实现中,线程之间存在多种状态转换,我们通过封装将不同状态封装为不同的对象,然后在不同状态之间分别相互触发状态转换;
状态模式扩展
在有些情况下,可能有多个环境对象需要共享一组状态,这时需要引入享元模式,将这些具体状态对象放在集合中供程序共享,其结构图如图所示:
共享状态模式的不同之处是在环境类中增加了一个 HashMap 来保存相关状态,当需要某种状态时可以从中获取,其程序代码如下:
class ShareContext
{
private ShareState state;
private HashMap<String, ShareState> stateSet=new HashMap<String, ShareState>();
public ShareContext()
{
state=new ConcreteState1();
stateSet.put("1", state);
state=new ConcreteState2();
stateSet.put("2", state);
state=getState("1");
}
//设置新状态
public void setState(ShareState state)
{
this.state=state;
}
//读取状态
public ShareState getState(String key)
{
ShareState s=(ShareState)stateSet.get(key);
return s;
}
//对请求做处理
public void Handle()
{
state.Handle(this);
}
}