在办公室,经常要打印文件。考虑一下打印机的工作原理:它主要有无纸状态、有纸状态、打印状态、停止状态。当按下打印按钮,打印机开始打印,如果发现没有纸,应该让打印机停下来,放入纸张后,才能继续打印,打印完毕,打印机停止。
实现这样的功能,首先想到的代码结构是这样的:定义一个打印机的接口,接口中定义打印机的各个状态和方法,代码如下:
package com.pattern.state.v1;
/**
* 打印机抽象接口
* @author
*
*/
public interface IPrinter {
public final static int STATE_PRINT=1;//开始打印
public final static int STATE_NOPAPER=2;//没有纸
public final static int STATE_HAVEPAPER=3;//已有纸张
public final static int STATE_STOP=4;//停止打印
public void setState(int state);
public void print();
public void nopaper();
public void havepaper();
public void stop();
}
然后定义具体实现类,利用if...else... 或者switch条件语句,根据不同的状态去执行不同的方法:
package com.pattern.state.v1;
/**
* 打印机实现类
* @author
*
*/
public class Printer implements IPrinter {
private int state=4;
@Override
public void setState(int state) {
this.state=state;
}
@Override
public void print() {//什么情况下开始打印
switch(this.state){
case STATE_PRINT://如果正在打印,则什么都不做
break;
case STATE_NOPAPER://如果没有纸,则什么都不做
break;
case STATE_HAVEPAPER://如果有纸了,则开始打印
this.printer_print();
this.setState(STATE_PRINT);
break;
case STATE_STOP://如果打印机是停止状态,则开始打印
this.printer_print();
this.setState(STATE_PRINT);
break;
}
}
@Override
public void stop() {//什么情况下让打印机停止
switch(this.state){
case STATE_PRINT://如果正在打印,则让打印机停止
this.printer_stop();
this.setState(STATE_STOP);
break;
case STATE_NOPAPER://如果没有纸,则打印机本来就是停止的,什么都不做
break;
case STATE_HAVEPAPER://如果有纸,让打印机停止
this.printer_stop();
this.setState(STATE_STOP);
break;
case STATE_STOP://若果打印机是停止状态,则什么都不做
break;
}
}
@Override
public void nopaper() {//什么情况下没纸
switch(this.state){
case STATE_PRINT://打印机正在打印,打印机没纸,打印机停止
this.printer_nopaper();
this.setState(STATE_NOPAPER);
this.printer_stop();
this.setState(STATE_STOP);
break;
case STATE_NOPAPER://如果没有纸,则什么都不做
break;
case STATE_HAVEPAPER://有纸了,则让打印机没纸
this.printer_nopaper();
this.setState(STATE_NOPAPER);
break;
case STATE_STOP://打印机停止状态,则让打印机没纸
this.printer_nopaper();
this.setState(STATE_NOPAPER);
break;
}
}
@Override
public void havepaper() {//什么情况下可以让打印机有纸
switch(this.state){
case STATE_PRINT://打印机正在打印,则什么都不做
break;
case STATE_NOPAPER://如果没有纸,则放入纸
this.printer_havepaper();
this.setState(STATE_HAVEPAPER);
break;
case STATE_HAVEPAPER://有纸了,则什么都不做
break;
case STATE_STOP://打印机停止状态,则放入纸张
this.printer_havepaper();
this.setState(STATE_HAVEPAPER);
break;
}
}
private void printer_havepaper(){
System.out.println("打印机被放入纸张,有纸了...");
}
private void printer_stop(){
System.out.println("打印机停止...");
}
private void printer_print(){
System.out.println("打印机开始打印...");
}
private void printer_nopaper(){
System.out.println("打印机没纸了...");
}
}
客户端测试类:
package com.pattern.state.v1;
/**
* 客户端测试:测试打印机一个完整的打印周期
* @author
*
*/
public class Client {
public static void main(String[] args) {
IPrinter p=new Printer();
p.print();//打印
p.nopaper();//打印机没纸
p.stop();//停止
p.havepaper();//放纸
p.print();//开始打印
p.stop();//打印完毕,停止
}
}
结果输出:
上面的代码能够实现基本功能,但是会有如下缺点:
1、随着”状态“的增多,会产生大量的if...else或者switch...语句, 代码扩展性极差
2、没有遵守"开放-关闭"原则
3、不符合面向对象思想
4、状态转换隐藏在条件语句中,任意改动都需要改变条件判断,容易导致BUG
那么,我们将如何改变程序结构呢?目前我们是通过行为来改变状态,为什么不能用状态来改变其行为呢?这就是我们今天的主题--状态模式。因为状态模式将状态封装成为独立的类,并将动作委托到代表当前状态的对象,行为就会随着内部状态而改变。
什么是状态模式呢?所谓状态模式就是当一个对象内在状态改变时允许其改变行为,这个对象看起来像是改变了其类。就是说状态模式封装的非常好,状态的变更引起了行为的变更,从外部看起来就好像这个对象对应的类发生了改变一样。
状态模式的通用类图如下:
从上图可以看出,状态模式所涉及到的角色有:
环境(Context)角色,也称上下文:定义客户端所感兴趣的接口,并且保留一个具体状态类的实例。这个具体状态类的实例给出此环境对象的现有状态。
抽象状态(State)角色:定义一个接口,用以封装环境(Context)对象的一个特定的状态所对应的行为。
具体状态(ConcreteState)角色:每一个具体状态类都实现了环境(Context)的一个状态所对应的行为。
用状态模式实现打印机功能。代码如下:
首先定义抽象状态接口:
package com.pattern.state.v2;
/**
* 打印机状态接口
* @author
*
*/
public interface IPrinterState {
public void nopaper();
public void havepaper();
public void print();
public void stop();
}
打印状态实现类:
package com.pattern.state.v2;
/**
* 打印机--打印状态
* @author
*
*/
public class PrintState implements IPrinterState {
private PrinterManager manager;
public PrintState(PrinterManager manager) {
super();
this.manager = manager;
}
@Override
public void havepaper() {
System.out.println("打印机正在打印,已经有纸...");
}
@Override
public void print() {
System.out.println("打印机开始打印...");
}
@Override
public void stop() {
System.out.println("打印机停止...");
this.manager.setState(this.manager.getStopState());
}
@Override
public void nopaper() {
System.out.println("打印机无纸...");
}
}
无纸状态实现类:
package com.pattern.state.v2;
/**
* 打印机--无纸状态
* @author
*
*/
public class NopaperState implements IPrinterState {
private PrinterManager manager;
public NopaperState(PrinterManager manager) {
super();
this.manager = manager;
}
@Override
public void havepaper() {
System.out.println("打印机被放入纸张...");
this.manager.setState(this.manager.getPutpaperState());
}
@Override
public void print() {
System.out.println("打印机无纸了,先停下放纸,再打印...");
this.manager.setState(this.manager.getStopState());
this.manager.setState(this.manager.getPutpaperState());
this.manager.setState(this.manager.getPrintState());
}
@Override
public void stop() {
System.out.println("打印机停止...");
this.manager.setState(this.manager.getStopState());
}
@Override
public void nopaper() {
System.out.println("打印机已经无纸...");
}
}
有纸状态实现类:
package com.pattern.state.v2;
/**
* 打印机--有纸状态
* @author
*
*/
public class HavepaperState implements IPrinterState {
private PrinterManager manager;
public HavepaperState(PrinterManager manager) {
super();
this.manager = manager;
}
@Override
public void havepaper() {
System.out.println("打印机已经有纸...");
}
@Override
public void print() {
System.out.println("打印机开始打印...");
this.manager.setState(this.manager.getPrintState());
}
@Override
public void stop() {
System.out.println("打印机停止...");
this.manager.setState(this.manager.getStopState());
}
@Override
public void nopaper() {
System.out.println("让打印机无纸...");
this.manager.setState(this.manager.getNopaperState());
}
}
停止状态实现类:
package com.pattern.state.v2;
/**
* 打印机--停止状态
* @author
*
*/
public class StopState implements IPrinterState {
private PrinterManager manager;
public StopState(PrinterManager manager) {
super();
this.manager = manager;
}
@Override
public void havepaper() {
System.out.println("打印机放入纸张...");
this.manager.setState(this.manager.getPutpaperState());
}
@Override
public void print() {
System.out.println("打印机开始打印...");
this.manager.setState(this.manager.getPrintState());
}
@Override
public void stop() {
System.out.println("打印机已经是停止状态...");
}
@Override
public void nopaper() {
System.out.println("让打印机无纸...");
this.manager.setState(this.manager.getNopaperState());
}
}
打印机状态管理系统,即环境上下文角色:
package com.pattern.state.v2;
/**
* 打印机状态管理系统:让打印机在不同的状态之间切换
* @author
*
*/
public class PrinterStateManager {
private IPrinterState nopaperState;//无纸状态
private IPrinterState havepaperState;//有纸状态
private IPrinterState printState;//打印状态
private IPrinterState stopState;//停止状态
private IPrinterState state=stopState;//默认停止状态
private boolean isOpen=false;//打印开关 打印?不打印?默认不打印
public PrinterStateManager(boolean pressOpen) {
nopaperState=new NopaperState(this);
havepaperState=new HavepaperState(this);
printState=new PrintState(this);
stopState=new StopState(this);
setIsOpen(pressOpen);
if(pressOpen)this.setState(printState);//若打开打印开关,则开始打印
}
public void setState(IPrinterState state){
this.state=state;
}
public IPrinterState getPutpaperState() {
return havepaperState;
}
public IPrinterState getPrintState() {
return printState;
}
public IPrinterState getNopaperState() {
return nopaperState;
}
public IPrinterState getStopState() {
return stopState;
}
public void putpaper(){
this.state.havepaper();
}
public void print(){
this.state.print();
}
public void stop(){
this.state.stop();
}
public void nopaper(){
this.state.nopaper();
}
public void setIsOpen(boolean isOpen) {
this.isOpen = isOpen;
}
}
客户端测试类:
package com.pattern.state.v2;
/**
* 客户端测试:一个完整的打印周期
* @author
*
*/
public class Client {
public static void main(String[] args) {
PrinterStateManager manager=new PrinterStateManager(true);
manager.print();
manager.nopaper();
manager.stop();
manager.putpaper();
manager.print();
manager.stop();
}
}
结果输出:
在状态模式中,环境(Context)是持有状态的对象,但是环境(Context)自身并不处理跟状态相关的行为,而是把处理状态的功能委托给了状态对应的状态处理类来处理。
在具体的状态处理类中经常需要获取环境(Context)自身的数据,甚至在必要的时候会回调环境(Context)的方法,因此,通常将环境(Context)自身当作一个参数传递给具体的状态处理类。
客户端一般只和环境(Context)交互。客户端可以用状态对象来配置一个环境(Context),一旦配置完毕,就不再需要和状态对象打交道了。客户端通常不负责运行期间状态的维护,也不负责决定后续到底使用哪一个具体的状态处理对象。
策略模式与状态模式的比较:
状态模式主要解决的是当控制一个对象状态转换的条件表达式过于复杂的情况。把状态的判断转移到表示不同状态的一系列类中,可以把复杂判断简化。
当一个状态的行为取决于他的状态,并且他必须在运行时刻根据状态改变他的行为时,可以考虑使用状态模式。
比较:从上面这几点,我们可以看出策略模式和状态模式的应用场景有很大的不同:一个是封装一系列平行且复杂多变的实现方式,一个是实现把对象的内在状态的变化封装起来,用外部行为来表现出来。
总之,虽然二者的类图很相似,但实际上解决的是不同情况的两种场景问题,需要我们去实际分析和判断。