状态模式(State Pattern)
状态模式:允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类
考虑如下场景,自动售票机:
每一个方格表示自动售票机的一种状态,每一个箭头线表示自动售票机由一个状态转换到另一个状态所需要的操作
状态抽象为自动售票机类的参数,操作抽象为自动售票机类的方法
创建一个实例变量持有状态值,方法内增加条件代码处理不同状态
public class TVM { final static int noCoin = 1; final static int hasCoin = 2; final static int ticketOut = 3; private int state = noCoin; public void insertCoin(){ if(state == noCoin){ this.state = hasCoin; System.out.println("Insert a coin!"); }else if(state == hasCoin){ System.out.println("You have already inserted a coin!"); }else{ System.out.println("The ticket is out,please wait!"); } } public void cancel(){ if(state == noCoin){ System.out.println("You do not insert a coin!"); }else if(state == hasCoin){ this.state = noCoin; System.out.println("Give the coin back to you!"); }else{ System.out.println("The ticket is out,can not cancel!"); } } public void confirm(){ if(state == noCoin){ System.out.println("You do not insert a coin!"); }else if(state == hasCoin){ this.state = ticketOut; System.out.println("Ticket out!"); }else{ System.out.println("The ticket is out,can not confirm!"); } } public void getTicket(){ if(state == noCoin){ System.out.println("There is not ticket to get!"); }else if(state == hasCoin){ System.out.println("There is not ticket to get!"); }else{ this.state = noCoin; System.out.println("Waiting for another passenger!"); } } }
public class Passenger { public static void main(String[] args) { TVM m = new TVM(); m.insertCoin(); m.cancel(); m.insertCoin(); m.confirm(); m.getTicket(); } }
结果:
Insert a coin! Give the coin back to you! Insert a coin! Ticket out! Waiting for another passenger!
当初设计的时候并没有考虑电子票会售完这个问题,现在要弥补这个错误:
按照之前的设计方式:
1,状态代码混杂于自动售票机类中,新增加一个“售罄”的状态,则自动售票机类就需进行大量修改,即状态的变更总会导致售票机类的修改
2,不同状态的代码混杂在一起,大量的if/else,极容易出错
根据“封装可变性”的设计原则,将可变的“状态”进行封装,以实现具体状态代码与售票机类之间的解耦
public interface Status { public void insertCoin(); public void cancel(); public void confirm(); public void getTicket(); }
public class NoCoin implements Status { private TVM tvm; public NoCoin(TVM tvm){ this.tvm = tvm; } public void insertCoin() { tvm.setStatus(tvm.getHasCoin()); System.out.println("Insert a coin!"); } public void cancel() { System.out.println("You do not insert a coin!"); } public void confirm() { System.out.println("You do not insert a coin!"); } public void getTicket() { System.out.println("There is not ticket to get!"); } }
public class HasCoin implements Status { private TVM tvm; public HasCoin(TVM tvm){ this.tvm = tvm; } public void insertCoin() { System.out.println("You have already inserted a coin!"); } public void cancel() { tvm.setStatus(tvm.getNoCoin()); System.out.println("Give the coin back to you!"); } public void confirm() { tvm.setStatus(tvm.getTicketOut()); System.out.println("Ticket out!"); } public void getTicket() { System.out.println("There is not ticket to get!"); } }
public class TicketOut implements Status { private TVM tvm; public TicketOut(TVM tvm){ this.tvm = tvm; } public void insertCoin() { System.out.println("The ticket is out,please wait!"); } public void cancel() { System.out.println("The ticket is out,can not cancel!"); } public void confirm() { System.out.println("The ticket is out,can not confirm!"); } public void getTicket() { if(tvm.getNum() > 0){ tvm.setNum(tvm.getNum() - 1); tvm.setStatus(tvm.getNoCoin()); System.out.println("Waiting for another passenger!"); }else{ tvm.setStatus(tvm.getSoldOut()); System.out.println("Sold out!"); } } }
public class SoldOut implements Status { private TVM tvm; public SoldOut(TVM tvm){ this.tvm = tvm; } public void insertCoin() { System.out.println("Sold out,can not insert coin!"); } public void cancel() { System.out.println("Sold out,can not cancel!"); } public void confirm() { System.out.println("Sold out,can not confirm!"); } public void getTicket() { System.out.println("Sold out,can not get ticket!"); } }
public class TVM { private NoCoin noCoin; private HasCoin hasCoin; private TicketOut ticketOut; private SoldOut soldOut; private Status status; private int num; public TVM(){ noCoin = new NoCoin(this); hasCoin = new HasCoin(this); ticketOut = new TicketOut(this); soldOut = new SoldOut(this); status = noCoin; num = 1; } public void insertCoin(){ status.insertCoin(); } public void cancel(){ status.cancel(); } public void confirm(){ status.confirm(); } public void getTicket(){ status.getTicket(); } public void setStatus(Status status){ this.status = status; } public Status getNoCoin(){ return noCoin; } public Status getHasCoin(){ return hasCoin; } public Status getTicketOut(){ return ticketOut; } public Status getSoldOut(){ return soldOut; } public int getNum() { return num; } public void setNum(int num) { this.num = num; } }
我们来测试一下:
public class Passenger { public static void main(String[] args) { TVM m = new TVM(); m.insertCoin(); m.cancel(); m.insertCoin(); m.confirm(); m.getTicket(); m.insertCoin(); m.confirm(); m.getTicket(); m.insertCoin(); } }
Insert a coin! Give the coin back to you! Insert a coin! Ticket out! Waiting for another passenger! Insert a coin! Ticket out! Sold out! Sold out,can not insert coin!
通过这个设计也可以看到,自动售票机类对修改关闭,状态类对扩展开放
经过重新设计,类的数量急剧增多,这是为了获得扩展性而付出的代价,其实真正重要的是暴漏给用户的类的数量
作为自动售票机的内部状态,用户没有必要知道其细节,我们也有能力将其隐藏,比起大块的if/else语句,代码也更容易理解与维护,利大于弊
看一下状态模式的类图,是不是和第二种设计一模一样:
PS:
状态模式和策略模式从类图上来看没有区别,本质上真的有什么区别么?
策略模式:除非告诉对象使用另一个策略,否则对象会一直使用其初始化时被赋予的策略
继承的一种替代方式
状态模式:初始化时告诉类从哪个状态开始,随着时间的变化,状态也会自动发生变化
大量条件判断语句的替代方式