目录
一、基础概念
二、UML类图
三、角色设计
四、案例分析
五、总结
状态模式允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类,状态模式主要解决的是当控制一个对象状态转换的条件表达式过于复杂时的情况,把状态的判断逻辑转移到表示不同状态的一系列类中,可以把复杂的判断逻辑简化。
角色 | 描述 |
---|---|
环境角色 | 维护一个State的实例,用来表示当前状态。并且定义了状态转换的set方法以及请求处理的业务方法。 |
抽象状态角色 | 定义一个接口封装与环境角色的一个特点接口相关行为 |
具体状态角色 | 实现抽象状态接口,封装状态对应的行为 |
下面案例通过状态模式模拟了一个电梯系统,电梯在开门、关门、运行、停止不同状态下的行为逻辑,以及在客户端调用之间状态的转变。
定义了电梯状态的抽象接口,包含开门、关门、运行、停止等操作。这对应状态模式中的抽象状态角色:
public interface LiftState {
/**
* 设置环境变量
* @param context
*/
public void setContext(Context context);
/**
* 电梯开启操作
*/
public void open();
/**
* 电梯关闭操作
*/
public void close();
/**
* 电梯运行操作
*/
public void run();
/**
* 电梯停止操作
*/
public void stop();
}
实现了LiftState接口,作为具体状态类,封装了对应的状态下电梯的行为逻辑。这对应具体状态角色:
public class OpeningState implements LiftState {
private Context context;
@Override
public void setContext(Context context) {
this.context = context;
}
@Override
public void open() {
//电梯门是打开的,不操作
System.out.println("电梯门已打开.....");
}
@Override
public void close() {
//电梯门打开,可以执行关闭操作
this.context.setLiftState(Context.CLOSING_STATE);
this.context.close();
}
@Override
public void run() {
//电梯开门的时候不可以运行
}
@Override
public void stop() {
//电梯能开门必须是停下的
}
}
public class ClosingState implements LiftState {
private Context context;
@Override
public void setContext(Context context) {
this.context = context;
}
@Override
public void open() {
//关门状态下可以开门
this.context.setLiftState(Context.OPENING_STATE);
this.context.open();
}
@Override
public void close() {
//已经时关门状态,无需关门操作
System.out.println("电梯门关闭.....");
}
@Override
public void run() {
//关了门自然可以运行
this.context.setLiftState(Context.RUNNING_STATE);
this.context.run();
}
@Override
public void stop() {
//电梯门关了但我不按楼层
this.context.setLiftState(Context.CLOSING_STATE);
this.context.stop();
}
}
public class RunningState implements LiftState {
private Context context;
@Override
public void setContext(Context context) {
this.context = context;
}
@Override
public void open() {
//运行状态下不可开门
}
@Override
public void close() {
//运行状态下已经是关好门的
}
@Override
public void run() {
//正在运行,无需操作
System.out.println("电梯正在运行.......");
}
@Override
public void stop() {
//运行可停下
this.context.setLiftState(Context.STOPPING_STATE);
this.context.stop();
}
}
public class StoppingState implements LiftState {
private Context context;
@Override
public void setContext(Context context) {
this.context = context;
}
@Override
public void open() {
//停下的状态时可以开门的
this.context.setLiftState(Context.OPENING_STATE);
this.context.open();
}
@Override
public void close() {
//停下的时候已经是关了门的
}
@Override
public void run() {
//停下也可以继续运行
this.context.setLiftState(Context.RUNNING_STATE);
this.context.run();
}
@Override
public void stop() {
//已经停下无需操作
System.out.println("电梯停止.......");
}
}
维护当前状态,提供请求委托和状态切换方法,对应的是环境角色:
public class Context {
//定义状态对象常量
public final static OpeningState OPENING_STATE = new OpeningState();
public final static ClosingState CLOSING_STATE = new ClosingState();
public final static RunningState RUNNING_STATE = new RunningState();
public final static StoppingState STOPPING_STATE = new StoppingState();
//定义当前电梯状态变量
private LiftState liftState;
public void setLiftState(LiftState liftState) {
this.liftState = liftState;
//将当前对象作为参数传递到状态对象的环境中
this.liftState.setContext(this);
}
//定义电梯的四个方法
public void open(){
this.liftState.open();
}
public void close(){
this.liftState.close();
}
public void run(){
this.liftState.run();
}
public void stop(){
this.liftState.stop();
}
}
客户端:
public class Client {
public static void main(String[] args) {
Context context = new Context();
context.setLiftState(new RunningState());
context.open();
context.close();
context.run();
context.stop();
}
}
运行结果如下:
执行流程如下:
1、Client调用Context的开门、运行接口。
2、Context委托请求给当前的状态对象。
3、状态对象执行状态对应的行为,如果需要转变状态,则调用Context切换状态。
4、Context切换状态变量到新状态,新的状态对象成为当前活动状态5. 新的状态对象接受请求,执行相应行为
综上,状态对象封装行为,Context维护状态,Client只与Context交互,这样将状态转移逻辑聚合到状态对象中,实现了与Context的解耦。
状态模式很好地封装了状态切换逻辑,消除了庞大的条件分支语句,提供了可维护的状态机。
优点:
1、封装了状态转换逻辑,将不同状态的行为局部化,避免Context中的复杂条件分支。
2、方便增加和删除状态,只需要改变状态类即可。
3、可以避免直接修改Context而引入不一致性。
4、提高了Context类的复用性。
缺点:
1、增加了类的数量,复杂度较高。
2、状态切换的条件分散在多个状态类中,不便维护。
3、状态类只能通过Context来间接访问资源,限制性较大。
应用场景:
1、一个对象的行为依赖于它的状态,并且它必须在运行时刻根据状态改变它的行为。
2、代码中包含大量与状态有关的条件语句,这些条件语句的作用是根据对象的状态选择不同的行为,这样会导致代码混乱和不易维护。
3、一个操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态。
4、需要管理的状态数量过多,这样就可以将不同的状态表示为不同的类。
5、系统中对象状态转换的规则比较复杂,状态模式可以将状态的转换逻辑集中到一个地方。
符合的设计原则:
1、开闭原则(Open Closed Principle)
状态模式可以在不修改对象自身的情况下,在运行时动态切换状态,扩展新的状态类。符合开闭原则的要求,对扩展开放,对修改关闭。
2、单一职责原则(Single Responsibility Principle)
每个状态类只负责对象在某一个状态下的行为。状态类之间互不影响,符合单一职责原则。
3、里氏替换原则(Liskov Substitution Principle)
子类可以扩展父类状态的行为,而不会破坏父类状态的行为预期。符合里氏替换原则。
4、依赖倒置原则(Dependency Inversion Principle)
状态类都依赖于抽象状态类,而不是具体的Context类。符合依赖倒置原则。
5、接口隔离原则(Interface Segregation Principle)
抽象状态类提供简单的行为方法,避免客户端需要不必要的接口。符合接口隔离原则。
6、组合聚合复用原则(Composite Reuse Principle)
客户端可以统一使用Context类,不需要与不同的具体状态类打交道。符合组合聚合复用原则。
7、迪米特法则(Law of Demeter)
Context通过抽象状态类与各具体状态类交互,符合迪米特法则的“只与直接朋友通信”。
总之,状态模式通过将复杂状态转换逻辑提取到不同的状态类中,可以提高代码的灵活性和可维护性,是一种比较常用和有效的设计模式。