在某些应用场景中,类的一些具体行为取决于其当前状态,类在执行的时候必须根据其内部保存的当前状态来执行具体行为,这种类可以叫做 有状态的类,类在执行具体函数的时候,先判断状态,然后根据状态来进行下一步的操作,操作完成后做状态的变更。
我们来举一个大家都用的栗子,我们常坐的电梯,一个电梯有开门,关门,停,运行等状态,而我们的具体行为要看电梯的状态而定,当我们按下电梯按钮后,电梯先判断自己的状态,如果现在电梯正在运行,那么不好意思,你要迟到了,爬楼梯吧。电梯的每一种状态改变,都有可能要根据其他状态来更新处理。例如,开门状态,你不能在运行的时候开门,而是在电梯定下后才能开门。我们来实现一下电梯使用的代码(java):
public class Lift {
//0:stopped,1:opened,2:running,3:closed;
private int nLiftStatus = 0;
public void open() {
if(nLiftStatus == 1)
{
System.out.println("the lift has opened already");
}
else if(nLiftStatus == 0 || nLiftStatus == 3)
{
System.out.println("the lift is opened successfully");
nLiftStatus = 1;
}
else
{
//do nothing
}
}
public void run() {
switch (nLiftStatus) {
case 0:
System.out.println("the lift begin to run");
nLiftStatus = 2;
break;
case 1:
//do nothing
break;
case 2:
//do nothing
break;
case 3:
System.out.println("the lift begin to run");
nLiftStatus = 2;
default:
break;
}
}
public void stop() {
switch (nLiftStatus) {
case 0:
//do nothing
break;
case 1:
//do nothing
break;
case 2:
System.out.println("the lift stopped successfully");
nLiftStatus = 0;
break;
case 3:
System.out.println("the lift stopped successfully");
nLiftStatus = 0;
default:
break;
}
}
public void close() {
switch (nLiftStatus) {
case 0:
System.out.println("the lift closed successfully");
nLiftStatus = 3;
break;
case 1:
System.out.println("the lift closed successfully");
nLiftStatus = 3;
break;
case 2:
//do nothing
break;
case 3:
//do nothing
default:
break;
}
}
}
应用场景如下:
public class Client {
public static void main(String[] args) {
// TODO Auto-generated method stub
Lift lift = new Lift();
lift.open();
lift.close();
lift.run();
lift.open();//没用的小伙子
lift.stop();
lift.open();
}
}
目前的代码足可以满足当前需求,但是考虑一下,如果依赖这个状态的接口不止4个,是十个,你就要做十次if else 判断,如果现在增加了一个状态:电梯维护中,那么你要在这十个if else 都要修改,这明显不符合开闭原则。针对这种现象:当一个对象内部状态的转换逻辑代码比较复杂,或者状态增加或修改的几率很大,这个时候就可以使用状态模式。
状态模式:当一个对象的内部状态发生改变时允许改变其行为,对象看起来似乎修改了它的类。
一、特征
1、内部状态:即,这个对象应该是有状态的对象。状态不是一般的命令,它是一个可以有多种值的属性,一般是一个枚举类型,状态会在对象具体行为后发生改变。
2、改变其行为:也就是说这个对象不再是行为驱动状态,而是状态决定行为。所以实现上,我们把所有与某个状态相关的行为都单独封装成类,在这些状态类中实现具体此状态下的行为并最后做这个对象的状态改变,所以这个对象中所有由状态决定行为的函数都应该在状态类中实现(抽象接口),并且在这些状态类中可以实时的改变这个对象的状态(对象要有设置状态类的接口)。
二、作用:
某些应用场景里,用户具体的行为根据状态不同而不同,而当此状态判断过于复杂或状态的种类发生改变的几率很大的时候,可以使用状态模式来将状态判断或种类改变的可能性进行封装,从而实现状态的判断于具体行为的解耦,这样使得代码更简洁,更易于扩展。
三、实现:
将与状态相关的行为进行抽象,并延迟到具体状态类中实现,抽象ICcontext类负责与状态相关的行为的抽象,ConcreteContext类负责定义其需要的具体状态类对象并作与状态无关的操作。当然,如果你的context不复杂且发生改变的几率不大,那么可以省略IContext类。
我们来看看如何使用状态模式实现电梯:
//IContext类,提供状态设置操作以及与状态相关的操作接口
public abstract class ILift {
IStatus m_Status;
public void setStatus(IStatus sta) {
m_Status = sta;
}
public void open() {
m_Status.open(this);
}
public void close() {
m_Status.close(this);
}
public void run() {
m_Status.run(this);
}
public void stop() {
m_Status.stop(this);
}
}
//concrete context 类,用来做其他不通用操作
public class Lift extends ILift{
public void doOtherthing1() {
System.out.println("do some thing 1");
}
public void doOtherthing2() {
System.out.println("do some thing 1");
}
}
//IStatus类,确定哪些操作与状态相关
public abstract class IStatus {
public abstract void open(ILift lift);
public abstract void close(ILift lift);
public abstract void run(ILift lift);
public abstract void stop(ILift lift);
}
//打开的状态
public class OpenStatus extends IStatus {
private static OpenStatus m_instance = null;
private OpenStatus() {}
public static IStatus getInstance() {
if(m_instance == null)
{
m_instance = new OpenStatus();
}
return m_instance;
}
public void open(ILift lift) {
System.out.println("the lift is opened successfully");
}
public void close(ILift lift) {
lift.setStatus(CloseStatus.getInstance());
lift.close();
}
public void run(ILift lift) {
//do nothing
}
public void stop(ILift lift) {
//do nothing
}
}
//关闭状态
public class CloseStatus extends IStatus{
private static CloseStatus m_instance = null;
private CloseStatus() {}
public static IStatus getInstance() {
if(m_instance == null)
{
m_instance = new CloseStatus();
}
return m_instance;
}
public void open(ILift lift) {
lift.setStatus(OpenStatus.getInstance());
lift.open();
}
public void close(ILift lift) {
System.out.println("the lift closed successfully");
}
public void run(ILift lift) {
lift.setStatus(RunStatus.getInstance());
lift.run();
}
public void stop(ILift lift)
{
lift.setStatus(StopStatus.getInstance());
lift.stop();
}
}
//运行状态
public class RunStatus extends IStatus{
private static RunStatus m_instance = null;
private RunStatus() {}
public static IStatus getInstance() {
if(m_instance == null) {
m_instance = new RunStatus();
}
return m_instance;
}
public void open(ILift lift) {
//do nothing
}
public void close(ILift lift) {
//do nothing
}
public void run(ILift lift) {
System.out.println("the lift begin to run");
}
public void stop(ILift lift) {
lift.setStatus(StopStatus.getInstance());
lift.stop();
}
}
//停止状态
public class StopStatus extends IStatus{
private static StopStatus m_instance = null;
private StopStatus() {}
public static IStatus getInstance() {
if(m_instance == null) {
m_instance = new StopStatus();
}
return m_instance;
}
public void open(ILift lift) {
lift.setStatus(OpenStatus.getInstance());
lift.open();
}
public void close(ILift lift) {
//do nothing
}
public void run(ILift lift) {
lift.setStatus(RunStatus.getInstance());
lift.run();
}
public void stop(ILift lift) {
System.out.println("the lift stopped successfully");
}
}
应用场景:
public class Client {
public static void main(String[] args) {
// TODO Auto-generated method stub
Lift lift = new Lift();
lift.setStatus(CloseStatus.getInstance());
lift.open();
lift.close();
lift.run();
lift.doOtherthing1();
lift.stop();
lift.run();
lift.open();//没用的小伙子
lift.stop();
lift.open();
}
}
这时候如果电梯增加了一个状态,只需要增加一个状态类并实现与状态相关的接口即可,不需要修改具体的电梯类,最多状态类间的切换会有一些改动。
三、缺点
从上面的栗子可以很清楚的知道,状态模式增加了代码的复杂性,并且他的使用场景只适合在状态切换复杂或状态变化的几率够大的情况下,现在想一下,电梯的变化点不是状态,而是具体行为(函数)数量易变,比如增加一个函数,那么所有状态类都需要增加一个函数并实现,其实复杂度也会变大。