顾名思义,这个模式是当类的行为基于其内部状态的时候,并且可以根据状态的改变而改变它的相关行为的时候
主要在代码上的表现就是代码中包含着许多If else语句,这时候或者你就应该提前想想是不是该使用状态模式了;稍后将会对状态模式进行分析
首先看一个错误的例子(Head First设计模式书中,我抄上吧),虽然它名字上体现了State,但实际上并不是状态模式,或者说没能达到状态模式的精髓;
public class GumballMachine {
final static int SOLD_OUT=0;
final static int No_QUARTER=1;
final static int HAS_QUARTER=2;
final static int SOLD=3;
int currentState=SOLD_OUT;
int count=0;
public GumballMachine(int count){
//进行初始化
this.count=count;
if(count>0){
this.currentState=No_QUARTER;
}
}
//定义行为
public void insertQuarter(){
if(currentState==HAS_QUARTER){
System.out.println("你不能再投一硬币");
}else if(currentState==No_QUARTER){
System.out.println("你投入了一个硬币");
currentState=HAS_QUARTER;
}else if(currentState==SOLD_OUT){
System.out.println("糖果机没糖果了,不要再往里面加钱了");
}else if(currentState==SOLD){
System.out.println("稍等一下");
}
}
public void ejectQuarter(){
//some operations like the one above;
}
//another functions
}
可以看到上面的代码方法中用了很多if else语句,并且类很明显与当前状态相关,此时改变出现,要求新增一个操作,此时你不得不修改其他所有方法的代码,因为状态转换是由Context(环境,在这就是糖果机,这个词挺关键,中文意指上下文语境,很多人翻译成上下文,当时听的懵逼,现在稍稍懂了一点,其实就是环境,或者说配置环境,为当前操作提供所需的所有参数,具体的操作依赖具体环境才能进行)完成的;
再稍微懂点状态模式的情况下,你得重新改写你的代码以适应新需求(这与开放-关闭原则相悖,事实上有经验的程序员会一开始就使用此模式,因为这种模式的出现情况很容易识别,而且像此类需求,你是凭借扩充是办不了的,必须修改之前代码)
新的代码是
抽出变化的部份,即状态
public interface State {
//定义糖果机行为,一般的情况下Context提供行为是确定的,变化的是状态,牢记这一点
void insertQuarter();
void ejectQuarter();
void turnCrank();
void dispense();
}
实现状态操作
public class NoQuarterState implements State {
private GumballMachine machine;
public NoQuarterState(GumballMachine context){
this.machine=context;
}
@Override
public void insertQuarter() {
//A state transition may occur
}
@Override
public void ejectQuarter() {
}
@Override
public void turnCrank() {
}
@Override
public void dispense() {
}
}
如上,状态转换是在状态实现类里面发生的,Context对象不接管状态的转换,状态被彻底抽了出来
新的糖果机
public class GumballMachine {
private State currentState=new NoQuarterState(this);
private int count=0;
public GumballMachine(int count){
//进行初始化
this.count=count;
}
//操作
public void insertQuarter(){
currentState.insertQuarter();
}
public void ejectQuarter(){
currentState.ejectQuarter();
}
// ...
public void setCurrentState(State state){
this.currentState=state;
}
}
看看此时新增加状态时将会怎么变,如新增加WinnerState
public class WinnerState implements State {
private GumballMachine machine;
public WinnerState(GumballMachine context){
this.machine=context;
}
@Override
public void insertQuarter() {
}
@Override
public void ejectQuarter() {
// A state transition may occur
}
@Override
public void turnCrank() {
}
@Override
public void dispense() {
}
}
因为状态转换完全封装在了状态对象里面,所以糖果机不需要改动
细心的同学可能会发现不少问题
首先 当新状态增加或者删除掉的时候,万一增加的状态对其他状态有依赖怎么办,还是得修改其他类的源代码,还是不行啊;
对于此类问题,我的理解是,谨慎设计代码后还是有状态明显以来其他状态,当某一状态发生修改,其他状态少不了要修改,这是免不了的,但是我们利用此模式,能将修改程度做到尽量小,在此模式中你只需修改与发生变化的状态有依赖的状态就好,而之前那种做法,你不得不修改每一个方法并且仔细检查,出错程度降低了,而且代码逻辑清晰,易于理解,别人一看你这是状态模式,就会联想到怎么处理
第二 实现上的问题 上面代码可以进一步优化,对Context这个东西而言,暴露了setState(State)方法,这对外部cliente而言是个很不知所措的接口,client会不知道怎么使用(因为他根本就不是给client使用的,是给State使用的),而且State作用范围也有待考量,很明显,像此类代码State只会服务于此糖果机,因为你在定义State时候定义的方法就是Context的方法,因为决定了State相关类只能服务于特定Context(此处希望正确理解),不应该暴露出去,而且根据我的经验判断,所有基于状态的类的设计都是这个套路
综合以上,新版的实现如下:
public class GumballMachine {
private State currentState;
private int count=0;
public GumballMachine(int count){
//进行初始化
this.count=count;
}
//操作
public void insertQuarter(){
currentState.insertQuarter();
}
public void ejectQuarter(){
currentState.ejectQuarter();
}
// ...对State开放的接口,不对client开放,因此声明为private,若此类是为继承而设计的类,请声明为protected或者酌情设计访问权限
private void setCurrentState(State state){
this.currentState=state;
}
//因为State接口只服务于Context,因而将其声明为 private static
private static interface State{
//定义糖果机行为,一般的情况下Context提供行为是确定的,变化的是状态,牢记这一点
void insertQuarter();
void ejectQuarter();
void turnCrank();
void dispense();
}
private static class NoQuarterState implements State{
private GumballMachine machine;
public NoQuarterState(GumballMachine context){
this.machine=context;
}
@Override
public void insertQuarter() {
//A state transition may occur
}
@Override
public void ejectQuarter() {
}
@Override
public void turnCrank() {
}
@Override
public void dispense() {
}
}
}
如上的实现觉得应该是比较好的了,如有细节或者心得讨论请联系我 2900250200 QQ
********************************************分割线*************************************************
与策略模式的区别,区别大了你问区别?当然都是用组合实现的,具体操作委托给相关组合对象了,但是里面差别很大,
1.目的上的区别是根本的,策略模式:将可以互换的行为封装起来,然后使用委托的方法决定使用哪一个行为,状态模式:封装基于状态的行为,并将行为委托到当前状态,不要因为都是用组合实现的,这意图上的就十分明显
2.实现上,如第一条 策略是可以互换的行为,由client完成,这就决定了Context需要向client暴露其setStrategy(Strategy),而状态是有依赖关系的,是转化过去,而非互换,由状态内部自己完成,一般不向client暴露;