1.定义
状态模式,又称状态机模式,属于行为型设计模式的一种,定义:允许一个对象在内部状态发生改变时改变其行为,就好像是改变了自身的类一样。
简单举个栗子,在传统方式下,我们根据一个对象的状态改变其行为的做法通常是这样的:
if obj.state == 1 then .... else...
if obj.state == 2 then .... else ...
如果状态简单,这种if else语句看上去是没有问题的,但是如果状态变得复杂起来,多种状态之间互相作用,关联,这种代码就会变得非常复杂,比如使用大量的flag去维护状态和状态之间的转换,使得这部分代码变得难以维护,最重要的是这部分代码将在客户端进行维护,客户端不得不关心各个状态之间的转换和具体的行为。
既然状态的转换和具体的行为难以在客户端维护,那根据面向对象的思考方式,就可以将这部分进行单独的抽象分离,将状态抽象出来成为单独的类,而状态的行为,转换自然也应该由状态对象来维护
(状态知道自己是谁,知道自己能够支持哪些行为,知道状态转换细节),客户端这个时候就不需要维护当前状态或者状态之间的转换。
这个过程本质上是利用了多态的原理,与策略模式有点类似,策略模式是将不同的处理方式抽象成一个相互独立的策略,策略之间是平等的,而状态模式强调的是平行性。
状态模式将对象的行为封装在不同的状态对象中,将对象的状态从对象中分离出来,客户端无需关心对象的当前状态和状态的抓换
状态模式角色:
- 抽象状态角色(State):状态类的抽象,应该定义维护状态所对应的行为方法
- 具体状态角色(Concrete State):具体状态类,实现相应的行为方法
- 上下文对象(Context):持有状态的对象,应包含状态对象的实例
UML:
图片来源:知乎
2.示例
以文件状态的切换为例,简单状态模式进行实现,假设一个文件有三种状态,分别是打开、关闭和移除,文件只能打开一次,当文件打开之后可以关闭,但不能删除,已被删除的文件所有的操作都是非法操作。
打开文件、关闭文件、删除文件这些相当于是可以影响状态的行为,也就是说不同的状态下有不同的行为。
首先对状态进行单独的抽象,并定义状态对应的行为
/**
* @description: 文件状态
* @version: 1.0
*/
public interface FileState {
void openFile(DocFile docFile);
void closeFile(DocFile docFile);
void removeFile(DocFile docFile);
}
对三种状态分别进行实现,定义各自状态的行为特征以及状态转换
/**
* @description: 打开文件状态
* @version: 1.0
*/
public class OpenState implements FileState{
@Override
public void openFile(DocFile docFile) {
System.out.println("当前文件已打开,请勿重复打开");
}
@Override
public void closeFile(DocFile docFile) {
//由状态来完成状态转换
docFile.setFileState(new CloseState());
System.out.println("当前文件已打开,现在关闭文件");
}
@Override
public void removeFile(DocFile docFile) {
System.out.println("非法操作,当前文件已打开,无法删除");
}
}
/**
* @description: 关闭文件状态
* @version: 1.0
*/
public class CloseState implements FileState{
@Override
public void openFile(DocFile docFile) {
docFile.setFileState(new OpenState());
System.out.println("当前文件未打开,现在打开文件");
}
@Override
public void closeFile(DocFile docFile) {
System.out.println("非法操作,当前文件未打开");
}
@Override
public void removeFile(DocFile docFile) {
docFile.setFileState(new RemoveState());
System.out.println("当前文件未打开,现在删除文件");
}
}
/**
* @description: 删除文件状态
* @version: 1.0
*/
public class RemoveState implements FileState{
@Override
public void openFile(DocFile docFile) {
System.out.println("非法操作,无法打开文件,文件不存在");
}
@Override
public void closeFile(DocFile docFile) {
System.out.println("非法操作,无法关闭文件,文件不存在");
}
@Override
public void removeFile(DocFile docFile) {
System.out.println("非法操作,无法删除文件,文件不存在");
}
}
最后定义一个上下文对象,这里可以是“文件对象”,文件对象应持状态实例,表示当前的状态
/**
* @description: context
* @version: 1.0
*/
public class DocFile {
//持有状态对象
private FileState fileState;
public FileState getFileState() {
return fileState;
}
public void setFileState(FileState fileState) {
this.fileState = fileState;
}
public void openFile(){
fileState.openFile(this);
}
public void closeFile(){
fileState.closeFile(this);
}
public void removeFile(){
fileState.removeFile(this);
}
}
测试:
/**
* @description: test
* @version: 1.0
*/
public class Test {
public static void main(String[] args) {
DocFile docFile = new DocFile();
docFile.setFileState(new CloseState());
docFile.openFile();
docFile.openFile();
docFile.removeFile();
docFile.closeFile();
docFile.removeFile();
docFile.openFile();
}
}
input:
当前文件未打开,现在打开文件
当前文件已打开,请勿重复打开
非法操作,当前文件已打开,无法删除
当前文件已打开,现在关闭文件
当前文件未打开,现在删除文件
非法操作,无法打开文件,文件不存在
状态模式中客户端只需要和上下文对象交互,不需要关心状态的细节信息,上下文对象持有状态对象,对外提供客户端期待的行为,同时又将具体的状态处理细节委托给了状态对象,这符合面向对象的设计原则,需要注意的是,在上下文对象委托状态对象行为的时候,大多数情况下需要将自身的this指针传递给状态,以便状态对象在合适的时机进行回调,从而反过来影响上下文对象的状态。
3.总结
状态模式使用单独的类来封装一个状态的处理,将具体的控制逻辑代码分散到不同的状态对象中,简化了控制逻辑,增强了扩展性,只需要增加新的状态实现类,并设置状态变化到这个新状态即可,当一个对象具有不同的状态,而在不同的状态下回产生不同的行为变化,或者一个操作中有大量的分支操作,这些分支依赖一个对象的状态,可以考虑使用状态设计模式。