12 State Pattern(状态模式)
前言:改变对象内部的状态来帮助对象控制自己的行为。
需求:
Vander的万能科技公司开发了一款自动售卖机,主要是为了节省人力成本,自动售卖机能自制橙汁,只需要投入10块,你就能获得一杯鲜榨橙汁:
上图有四种状态,每一个动作(投入10元、按下退钱按钮、按下出售按钮,果汁出柜[自动售卖机的内部动作])都有可能会触发糖果机进入下个状态。下面我们看看代码实现:
AutoSeller:
public class AutoSeller {
private final static int SOLD_OUT = 0;
private final static int NO_TEN_YUAN = 1;
private final static int HAS_TEN_YUAN = 2;
private final static int SOLD = 3;
int state = SOLD_OUT;
int count = 0;
public AutoSeller(int count) {
this.count = count;
if(count > 0) {
state = NO_TEN_YUAN;
}
}
public void insertTenYuan() {
if(state == SOLD_OUT) {
System.out.println("The machine is sold out, please push refund button!");
} else if(state == NO_TEN_YUAN) {
state = HAS_TEN_YUAN;
System.out.println("You insert ten yuan!");
} else if(state == HAS_TEN_YUAN) {
System.out.println("The machine has ten yuan, don't repeat doing it");
} else if(state == SOLD) {
System.out.println("Please wait, we're already giving you a cup of judge");
}
}
public void ejectTenYuan() {
if(state == SOLD_OUT) {
System.out.println("You can't eject, you haven't insert ten yuan");
} else if(state == NO_TEN_YUAN) {
System.out.println("You haven't insert ten yuan");
} else if(state == HAS_TEN_YUAN) {
System.out.println("Ten yuan returned");
state = NO_TEN_YUAN;
} else if(state == SOLD) {
System.out.println("Sorry, you already press the sell button");
}
}
public void pressSellButton() {
if(state == SOLD_OUT) {
System.out.println("You press the sell button, but there is no judge");
} else if(state == NO_TEN_YUAN) {
System.out.println("You press the sell button, you haven't insert ten yuan");
} else if(state == HAS_TEN_YUAN) {
System.out.println("You press the sell button, give you the judge");
state = SOLD;
} else if(state == SOLD) {
System.out.println("Sorry, you already press the sell button");
}
}
public void dispense() {
if(state == SOLD_OUT) {
System.out.println("System error, No judge dispense");
} else if(state == NO_TEN_YUAN) {
System.out.println("System error, No judge dispense");
} else if(state == HAS_TEN_YUAN) {
System.out.println("System error, No judge dispense");
state = SOLD;
} else if(state == SOLD) {
System.out.println("We're giving the judge");
count--;
if(count == 0) {
System.out.println("The judge has sold out");
state = SOLD_OUT;
} else {
state = NO_TEN_YUAN;
}
}
}
}
Main:
public class Main {
public static void main(String[] args) {
AutoSeller autoSeller = new AutoSeller(10);
autoSeller.insertTenYuan();
autoSeller.pressSellButton();
autoSeller.dispense();
autoSeller.dispense();
autoSeller.ejectTenYuan();
autoSeller.insertTenYuan();
autoSeller.ejectTenYuan();
}
}
实现效果:
需求变更:正当Vander对自己写的代码非常满意的时候,接到买家的变更需求,需要搞个促销活动,当售出按钮被按下的时候,有10分之一的机会会获得两杯果汁,也就是说10个人中有一个能获得两杯果汁。
最直接的想法肯定是直接在dispense的时候,加入随机数然后随机数是1-10的话,就算是中奖了,就给两杯果汁,若不是则是一杯果汁,但是这样有个问题万一营销活动结束之后,是不是又得改动这一大堆的if语句,这样是否违反了设计的原则。
1、没有遵守开放-关闭原则
2、不符合面向对象,这份代码完全是面向过程的编程
3、状态转换埋藏在条件语句中,所以并不明显
4、没有把变化的部分包装起来
5、未来加入新功能很可能会导致旧功能出问题
我们先遵守面向对象的原则进行,将每个state用对象表示,然后AutoSeller委托每个状态对象来进行相应的操作,这里是否很像策略模式,我们让这些状态都继承State接口,在这个接口内,自动售卖机的每个动作都有对应的方法。下面先来看看类图:
下面直接看代码实现
State:
public interface State {
public void insertTenYuan();
public void ejectTenYuan();
public void pressSellButton();
public void dispense();
}
NoTenYuanState:
public class NoTenYuanState implements State {
private AutoSeller autoSeller;
public NoTenYuanState(AutoSeller autoSeller) {
this.autoSeller = autoSeller;
}
public void insertTenYuan() {
System.out.println("You insert ten yuan!");
autoSeller.setState(autoSeller.getHasTenYuanState());
}
public void ejectTenYuan() {
System.out.println("You haven't insert ten yuan");
}
public void pressSellButton() {
System.out.println("You press the sell button, you haven't insert ten yuan");
}
public void dispense() {
System.out.println("System error");
}
}
HasTenYuanState:
public class HasTenYuanState implements State {
private AutoSeller autoSeller;
public HasTenYuanState(AutoSeller autoSeller) {
this.autoSeller = autoSeller;
}
public void insertTenYuan() {
System.out.println("The machine has ten yuan, don't repeat doing it");
}
public void ejectTenYuan() {
System.out.println("Ten yuan returned");
autoSeller.setState(autoSeller.getNoTenYuanState());
}
public void pressSellButton() {
System.out.println("You press the sell button, give you the judge");
autoSeller.setState(autoSeller.getSoldState());
}
public void dispense() {
System.out.println("System error");
}
}
SoldState:
public class SoldState implements State {
private AutoSeller autoSeller;
public SoldState(AutoSeller autoSeller) {
this.autoSeller = autoSeller;
}
public void insertTenYuan() {
System.out.println("Please wait, we're already giving you a cup of judge");
}
public void ejectTenYuan() {
System.out.println("Sorry, you already press the sell button");
}
public void pressSellButton() {
System.out.println("Sorry, you already press the sell button");
}
public void dispense() {
autoSeller.dispense();
if(autoSeller.getCount() > 0) {
autoSeller.setState(autoSeller.getNoTenYuanState());
} else {
autoSeller.setState(autoSeller.getSoldOutState());
}
}
}
SoldOutState:
public class SoldOutState implements State {
AutoSeller autoSeller;
public SoldOutState(AutoSeller autoSeller) {
this.autoSeller = autoSeller;
}
public void insertTenYuan() {
System.out.println("You insert ten yuan, but there is no judge");
}
public void ejectTenYuan() {
System.out.println("You can't eject, you haven't insert ten yuan");
}
public void pressSellButton() {
System.out.println("You press the sell button, but there is no judge");
}
public void dispense() {
System.out.println("System error");
}
}
AutoSeller:
public class AutoSeller {
private State noTenYuanState;
private State hasTenYuanState;
private State soldOutState;
private State soldState;
private int count;
private State state = soldOutState;
public AutoSeller(int count) {
super();
this.noTenYuanState = new NoTenYuanState(this);
this.hasTenYuanState = new HasTenYuanState(this);
this.soldOutState = new SoldOutState(this);
this.soldState = new SoldState(this);
this.count = count;
if(count > 0) {
state = noTenYuanState;
}
}
public void insertTenYuan() {
state.insertTenYuan();
System.out.println(state.getClass().getSimpleName());
}
public void ejectTenYuan() {
state.ejectTenYuan();
System.out.println(state.getClass().getSimpleName());
}
public void pressSellButton() {
state.pressSellButton();
System.out.println(state.getClass().getSimpleName());
state.dispense();
System.out.println(state.getClass().getSimpleName());
}
public void dispense() {
System.out.println("Giving you a cup of judge");
if(count != 0) {
count = count - 1;
}
}
public void setState(State state) {
this.state = state;
}
public int getCount() {
return count;
}
public State getNoTenYuanState() {
return noTenYuanState;
}
public void setNoTenYuanState(State noTenYuanState) {
this.noTenYuanState = noTenYuanState;
}
public State getHasTenYuanState() {
return hasTenYuanState;
}
public void setHasTenYuanState(State hasTenYuanState) {
this.hasTenYuanState = hasTenYuanState;
}
public State getSoldOutState() {
return soldOutState;
}
public void setSoldOutState(State soldOutState) {
this.soldOutState = soldOutState;
}
public State getSoldState() {
return soldState;
}
public void setSoldState(State soldState) {
this.soldState = soldState;
}
}
Main:
public class Main {
public static void main(String[] args) {
AutoSeller autoSeller = new AutoSeller(2);
autoSeller.insertTenYuan();
autoSeller.pressSellButton();
autoSeller.ejectTenYuan();
autoSeller.insertTenYuan();
autoSeller.ejectTenYuan();
autoSeller.insertTenYuan();
autoSeller.pressSellButton();
autoSeller.insertTenYuan();
autoSeller.pressSellButton();
}
}
实现效果:
我们先来捋一捋思路,我们将每个状态实现为不同的状态类,然后将状态类装配给AutoSeller,AutoSeller进行每个操作(投入10元、退钱、按下按钮等)的时候调用不同的状态类来完成相应的动作。这里跟策略模式很类似,策略模式把行为封装成类,装配了该对象的就具备相应的功能,所以说策略模式跟状态模式本来算是孪生兄弟,只是后来大家走的路线不一致而已。接着感受一下当前的这种有什么好处:
1、将每个状态的行为局部化到它自己的类中
2、将容易产生问题的if语句删除,以方便日后的维护
3、让每个状态“对修改关闭”,让AutoSeller“对扩展开放”,因为可以加入新的状态来控制AutoSeller的行为。
4、创建一个新的代码基和类的结构,更容易阅读和理解。
不妨将自己当成机器,来尝尝状态转移的感觉吧。
下面来定义一下状态模式
状态模式:允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。
这句话可能有点难理解,先前半句,在内部状态改变时改变它的行为,例如现在AutoSeller
的state是HasTenYuan,HasTenYuan的情况下进行pressSoldButton肯定跟NoTenYuan的情况下进行pressSoldButton的处理不一样。后半句,看起来好像修改了它的类,从客户的视角来看:如果说你是哟共的对象能够完全改变它的行为,那么你会觉得,这个对象实际上是从别的类实例化而来的,然而实际上只是用组合通过简单引用不同的状态对象来造成这样的假象。
我们接着来看看状态模式的类图:
状态模式关键的部分就是所有的ConcreteState都继承了state这个接口,这样的好处就是状态可以增加跟减少,而不需要对Context进行大改,只需要装配多个状态而已,如果结合Spring的装配的话,可以将所有State放入Map中,就不需要对Context进行改变了。
还有一个关键的部分就是,Context调用request方法的时候,Context内部的state会改变,这样就做到了调用不同的state来做事情。
接下来接着没有完成的事情,添加多一个WinnerState,大概的设计思路:
1、在AutoSeller中装配WinnerState。
2、WinnerState只有Dispense方法有效,其余方法都输出系统错误。
3、修改HasTenYuanState的pressSoldButton方法,在按下按钮之后产生0-9的随机数,若随机数为0,则进入WinnerState状态,若不为0,则进入普通的SoldState状态。
废话不多说,直接上代码:
WinnerState:
public class WinnerState implements State {
private AutoSeller autoSeller;
public WinnerState(AutoSeller autoSeller){
this.autoSeller = autoSeller;
}
public void insertTenYuan() {
System.out.println("System error");
}
public void ejectTenYuan() {
System.out.println("System error");
}
public void pressSellButton() {
System.out.println("System error");
}
public void dispense() {
System.out.println("You're the winner!");
autoSeller.dispense();
if(autoSeller.getCount() == 0) {
autoSeller.setState(autoSeller.getSoldOutState());
} else {
autoSeller.dispense();
if(autoSeller.getCount() > 0) {
autoSeller.setState(autoSeller.getNoTenYuanState());
} else {
System.out.println("Oops, out of judge!");
autoSeller.setState(autoSeller.getSoldOutState());
}
}
}
}
HasTenYuanState:
public class HasTenYuanState implements State {
private AutoSeller autoSeller;
public HasTenYuanState(AutoSeller autoSeller) {
this.autoSeller = autoSeller;
}
public void insertTenYuan() {
System.out.println("The machine has ten yuan, don't repeat doing it");
}
public void ejectTenYuan() {
System.out.println("Ten yuan returned");
autoSeller.setState(autoSeller.getNoTenYuanState());
}
public void pressSellButton() {
System.out.println("You press the sell button, give you the judge");
Random random = new Random();
int randomNum = random.nextInt(9);
if(randomNum == 0) {
autoSeller.setState(autoSeller.getWinnerState());
} else {
autoSeller.setState(autoSeller.getSoldState());
}
}
public void dispense() {
System.out.println("System error");
}
}
AutoSeller:(主要就是装配了WinnerState)
public class AutoSeller {
private State noTenYuanState;
private State hasTenYuanState;
private State soldOutState;
private State soldState;
private State winnerState;
private int count;
private State state = soldOutState;
public AutoSeller(int count) {
super();
this.noTenYuanState = new NoTenYuanState(this);
this.hasTenYuanState = new HasTenYuanState(this);
this.soldOutState = new SoldOutState(this);
this.soldState = new SoldState(this);
this.winnerState = new WinnerState(this);
this.count = count;
if(count > 0) {
state = noTenYuanState;
}
}
public void insertTenYuan() {
state.insertTenYuan();
System.out.println(state.getClass().getSimpleName());
}
public void ejectTenYuan() {
state.ejectTenYuan();
System.out.println(state.getClass().getSimpleName());
}
public void pressSellButton() {
state.pressSellButton();
System.out.println(state.getClass().getSimpleName());
state.dispense();
System.out.println(state.getClass().getSimpleName());
}
public void dispense() {
System.out.println("Giving you a cup of judge");
if(count != 0) {
count = count - 1;
}
}
public void setState(State state) {
this.state = state;
}
public int getCount() {
return count;
}
public State getNoTenYuanState() {
return noTenYuanState;
}
public void setNoTenYuanState(State noTenYuanState) {
this.noTenYuanState = noTenYuanState;
}
public State getHasTenYuanState() {
return hasTenYuanState;
}
public void setHasTenYuanState(State hasTenYuanState) {
this.hasTenYuanState = hasTenYuanState;
}
public State getSoldOutState() {
return soldOutState;
}
public void setSoldOutState(State soldOutState) {
this.soldOutState = soldOutState;
}
public State getSoldState() {
return soldState;
}
public void setSoldState(State soldState) {
this.soldState = soldState;
}
public State getWinnerState() {
return winnerState;
}
public void setWinnerState(State winnerState) {
this.winnerState = winnerState;
}
}
添加多了一个状态,对于用户来说实际上是没有感知的,Main类就作为用户来调用AutoSeller,Main完全不需要进行改变,我们将一群行为封装在状态对象中,context的行为随时可委托到哪些状态对象中的一个,随着运行的过程,当前状态在状态对象结合中游走改变以反映出context内部的状态。这么听上去好像跟策略模式也挺像的,下面看看这两个模式的差异。
状态模式与策略模式对比:
模式 | 状态模式 | 策略模式 |
相同点 | 主要都是使用组合来实现功能 | |
不同点 | 客户通常主动指定Context所要组合的策略对象是哪个,策略模式让类具有弹性,能够在运行时改变策略,但是对于某个Context对象而言总会有个最适当的策略对象,比如说绿头鸭的飞行行为和橡皮鸭的飞行行为就是装配确定的飞行对象。 策略模式更多的是除了继承之外的一种弹性替代方法 | 状态模式更多是想不用在context中放置很多条件判断的替代方案。通过将行为包装进状态对象中,你可以通过context简单地改变状态对象来改变context的行为。 |
状态模式也会有它的缺点,由于在个别的状态类中封装状态行为,结果总是增加这个设计的类的数目。这就是为了获取弹性而付出代价。
我们再回顾一下刚刚添加WinnerState的过程,实际上WinnerState就只完成了一个dispense的功能,跟SoldState其实基本类似,当然也可以将发放两颗糖果的代码放在SoldState里面,这么做就等于是将两个状态用一个状态类来表示了,这么做会牺牲了状态类的清晰易懂来减少一些冗余的代码,这也就不符合单一责任原则了,有没有考虑这么做之后,在促销活动结束之后,需要改动的地方就多了,而不是仅仅地修改HasTenYuanState。
最后又到了喜闻乐见的总结部分,我们又来总结我们现在现有的设计模式武器。
面向对象基础
抽象、封装、多态、继承
八大设计原则
设计原则一:封装变化
设计原则二:针对接口编程,不针对实现编程。
设计原则三:多用组合,少用继承。
设计原则四:为交互对象之间的松耦合设计而努力
设计原则五:对扩展开放,对修改关闭
设计原则六:依赖抽象,不要依赖于具体的类
设计原则七:只和你的密友谈话
设计原则八:别找我,我有需要会找你
设计原则九:类应该只有一个改变的理由
模式
状态模式:允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。
最后献上此次设计所涉及到的源码,有需要的小伙伴可以下载来运行一下,首先先自己进行设计,然后再参考,这样才能加深模式的理解。