状态模式

《大话设计模式》第16章读书笔记,介绍状态模式

Posted by GrayWind on November 16, 2018

以下面这段工作加班状态判断程序为例,我们可以看到对于当前状态的判断逻辑很复杂,并且如果我们想对17-22点这段时间的判断逻辑增加20点必须下班,那么需要直接进行改动,这就违反了开放-封闭原则。对于这类情况可以使用状态模式。

public class Work {
	private int hour;
	private boolean finish = false;

	public int getHour() {
		return hour;
	}

	public void setHour(int hour) {
		this.hour = hour;
	}

	public boolean isFinish() {
		return finish;
	}

	public void setFinish(boolean finish) {
		this.finish = finish;
	}

	public void writeProgram() {
		if (hour < 12) {
			System.out.printf("当前时间:%d点 上午工作,精神百倍\n", hour);
		} else if (hour < 13) {
			System.out.printf("当前时间:%d点 饿了,午饭;犯困,午休。\n", hour);
		} else if (hour < 17) {
			System.out.printf("当前时间:%d点 下午状态还不错,继续努力\n", hour);
		} else {
			if (finish) {
				System.out.printf("当前时间:%d点 下班回家了\n", hour);
			} else {
				if (hour < 21) {
					System.out.printf("当前时间:%d点 加班哦,疲累之极\n", hour);
				} else {
					System.out.printf("当前时间:%d点 不行了,睡着了。\n", hour);
				}
			}
		}
	}
	
	public static void main(String[] args) {
		//紧急项目
        Work emergencyProjects = new Work();
        emergencyProjects.setHour(9);
        emergencyProjects.writeProgram();
        emergencyProjects.setHour(10);
        emergencyProjects.writeProgram();
        emergencyProjects.setHour(12);
        emergencyProjects.writeProgram();
        emergencyProjects.setHour(13);
        emergencyProjects.writeProgram();
        emergencyProjects.setHour(14);
        emergencyProjects.writeProgram();
        emergencyProjects.setHour(17);

        //emergencyProjects.WorkFinished = true;
        emergencyProjects.setFinish(false);

        emergencyProjects.writeProgram();
        emergencyProjects.setHour(19);
        emergencyProjects.writeProgram();
        emergencyProjects.setHour(22);
        emergencyProjects.writeProgram();
	}
}

状态模式(State),当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。状态模式主要解决的是当控制一个对象状态转换的条件表达式过于复杂时的情况。把状态的判断逻辑转移到表示不同状态的一系列类当中,可以把复杂的判断逻辑简化。当然,如果这个状态判断很简单,那就没比较用状态模式了。

state

State类,抽象状态类,定义一个接口以封装与Context的一个特定状态相关的行为

public interface State {
	public void handle(Context context);
}

ConcreteState类,具体状态,每一个子类实现一个与Context的一个状态相关的行为。

public class ConcreteStateA implements State {

	@Override
	public void handle(Context context) {
		context.setState(new ConcreteStateB());
	}

}

public class ConcreteStateB implements State {

	@Override
	public void handle(Context context) {
		context.setState(new ConcreteStateA());
	}

}

Context类,维护一个ConcreteState子类的实例,这个实例定义当前的状态

public class Context {
	private State state;

	//定义Context初始状态
	public Context(State state) {
		this.state = state;
	}

	public State getState() {
		return state;
	}

	public void setState(State state) {
		this.state = state;
		System.out.println("当前状态:" + state.getClass().getName());
	}

	//对请求做处理,并设置下一个状态
	public void Request() {
		state.handle(this);
	}
}

客户端代码

public class StateTest {
	public static void main(String[] args) {
		Context c = new Context(new ConcreteStateA());
		c.Request();//当前状态:graywind.design.state.ConcreteStateB
		c.Request();//当前状态:graywind.design.state.ConcreteStateA
		c.Request();//当前状态:graywind.design.state.ConcreteStateB
		c.Request();//当前状态:graywind.design.state.ConcreteStateA
	}
}

状态模式的好处是将与特定状态相关的行为局部化,并且将不同状态的行为分割开来。将特定的状态相关的行为都放入一个对象中,由于所有与状态相关的代码都存在于某个ConcreteState中,所以通过定义新的子类可以很容易地增加新的状态和转换。这样做的目的是为了消除庞大的条件分支语句。状态模式通过把各种状态转移逻辑分布到State的子类之间,来减少相互间的依赖。当一个对象的行为取决于它的状态,并且必须在运行时刻根据状态改变它的行为时,就可以考虑使用状态模式。

下面用状态模式来重写上面的实例程序。

statework

首先,抽象状态类定义一个抽象方法——写程序

public interface WorkState {
	public void writeProgram(StateWork w);
}

上午、中午、下午的工作类分别根据时间判断要不要进去下一层

//上午工作状态
public class ForenoonState implements WorkState {

	@Override
	public void writeProgram(StateWork w) {
		if (w.getHour() < 12) {
			System.out.printf("当前时间:%d点 上午工作,精神百倍\n", w.getHour());
		} else {
			w.setCurrent(new NoonState());
			w.writeProgram();
		}
	}

}

//中午工作状态
public class NoonState implements WorkState {

	@Override
	public void writeProgram(StateWork w) {
		if (w.getHour() < 13) {
			System.out.printf("当前时间:%d点 饿了,午饭;犯困,午休。\n", w.getHour());
		} else {
			w.setCurrent(new AfternoonState());
			w.writeProgram();
		}
	}

}

//下午工作状态
public class AfternoonState implements WorkState {

	@Override
	public void writeProgram(StateWork w) {
		if (w.getHour() < 17) {
			System.out.printf("当前时间:%d点 下午状态还不错,继续努力\n", w.getHour());
		} else {
			w.setCurrent(new EveningState());
			w.writeProgram();
		}
	}

}

晚间工作状态需要先判断工作完成状态,再考虑时间

//晚间工作状态
public class EveningState implements WorkState {

	@Override
	public void writeProgram(StateWork w) {
		if (w.isFinish()) {
			w.setCurrent(new RestState());//工作已经结束转入下班状态
			w.writeProgram();
		} else {
			if (w.getHour() < 21) {
				System.out.printf("当前时间:%d点 加班哦,疲累之极\n", w.getHour());
			} else {
				w.setCurrent(new SleepingState());//超过21点转入睡眠状态
				w.writeProgram();
			}
		}
	}

}

下班和睡眠状态是逻辑判断分支的结束

//下班状态
public class RestState implements WorkState {

	@Override
	public void writeProgram(StateWork w) {
		System.out.printf("当前时间:%d点 下班回家了\n", w.getHour());
	}

}

//睡眠状态
public class SleepingState implements WorkState {
	
	@Override
	public void writeProgram(StateWork w) {
		System.out.printf("当前时间:%d点 不行了,睡着了。\n", w.getHour());
	}
	
}

工作类此时没有了过长的分支判断语句

public class StateWork {
	//钟点属性,状态装换的依据
	private int hour;
	//任务完成属性,能否下班的依据
	private boolean finish = false;
	private WorkState current;

	public StateWork() {
		this.current = new ForenoonState();
	}

	public int getHour() {
		return hour;
	}

	public void setHour(int hour) {
		this.hour = hour;
	}

	public boolean isFinish() {
		return finish;
	}

	public void setFinish(boolean finish) {
		this.finish = finish;
	}

	public void setCurrent(WorkState current) {
		this.current = current;
	}
	
	public void writeProgram() {
		current.writeProgram(this);
	}
}

客户端代码和之前相比没有改动,但是程序变得更加灵活易于修改

public class StateWorkTest {
	public static void main(String[] args) {
		//紧急项目
		StateWork emergencyProjects = new StateWork();
        emergencyProjects.setHour(9);
        emergencyProjects.writeProgram();
        emergencyProjects.setHour(10);
        emergencyProjects.writeProgram();
        emergencyProjects.setHour(12);
        emergencyProjects.writeProgram();
        emergencyProjects.setHour(13);
        emergencyProjects.writeProgram();
        emergencyProjects.setHour(14);
        emergencyProjects.writeProgram();
        emergencyProjects.setHour(17);

        //emergencyProjects.WorkFinished = true;
        emergencyProjects.setFinish(false);

        emergencyProjects.writeProgram();
        emergencyProjects.setHour(19);
        emergencyProjects.writeProgram();
        emergencyProjects.setHour(22);
        emergencyProjects.writeProgram();
	}
}