• 状态模式


    使用场景

    业务中比较多的状态,不同状态下需要做的事情不同。这样,每个步骤中都需要判断一下当前属于什么状态,状态过滤完成以后对相应的状态作出处理。当前状态执行完成以后,可能需要根据条件进入下一个状态(可能是下一个状态,可能是上一个状态,可能是终止或者回到初始状态等)。这种逻辑判断完以后一般都会出现比较多的if...else或者switch...case.最可怕的是后面如果加入一个状态或者修改状态相应的处理动作,这样就比较麻烦了。状态模式通过把状态拆分成不同的状态类来拆解条件。这样,在以后改变需求时具有比较好的扩展性。

    具体例子

    这里举《Head First 设计模式》中的一个例子。
    模拟一个糖果机销售糖果的过程。在没有投入钱币时,糖果机的状态为 没有25分钱 如果有人投入了25分钱,那么状态为 有25分钱。有25分钱的状态下可以转动曲柄,走到下一个状态 售出糖果。售出糖果以后 如果糖果数大于零,糖果机回到 没有25分钱 状态。 否则,如果糖果数等于零,那么到达 糖果售罄 状态。此时如果在投入25分钱,那么将执行退回25分钱操作。具体状态转化如下图所示:
    由图可知:
    糖果机的状态包括:没有25分钱,有25分钱,售出糖果,糖果售罄 四种状态。糖果机的动作包括:投入25分钱,退回25分钱,转动曲柄,发放糖果 等动作
    对于每一步操作,都需要判断状态,然后做出动作,接着转换成相应的状态。
    把上述状态和动作转换为代码,如下所示:
     1 public class CandyMachine{
     2     private final int SOLD_OUT   = 0;//售罄
     3     private final int NO_QUARTZ  = 1;//没有25分钱
     4     private final int HAS_QUARTZ = 2;//有25分钱
     5     private final int SOLD       = 3;//售出
     6     
     7     private int state = SOLD_OUT;//初始化状态值state为售罄状态
     8     private int count = 0;//糖果数目
     9     
    10     public CandyMachine(int count){
    11         this.count = count;
    12         if(count > 0){
    13             this.state = NO_QUARTZ;
    14         }
    15     }
    16     
    17     //投入25分钱
    18     public void insertQuartz(){
    19         if(state == HAS_QUARTZ){
    20             System.out.println("已经投了钱,不能重复投");
    21         }else if(state == NO_QUARTZ){
    22             state = HAS_QUARTZ;
    23             System.out.println("投币成功");
    24         }else if(state == SOLD_OUT){
    25             System.out.println("不能投币,糖果已售罄");
    26         }else if(state == SOLD) {
    27             System.out.println("请等待,正在发放糖果");
    28         }
    29     }
    30     
    31     //退回25分钱
    32     public void ejectQuartz(){
    33         if(state == HAS_QUARTZ){
    34             System.out.println("正在退钱");
    35         }else if(state == NO_QUARTZ){
    36             System.out.println("未投钱,不能退钱");
    37         }else if(state == SOLD_OUT){
    38             System.out.println("不能退币,糖果已售罄");
    39         }else if(state == SOLD) {
    40             System.out.println("已经转动曲柄,不能退钱");
    41         }
    42     }
    43     
    44     //转动曲柄
    45     public void turnCrank(){
    46         if(state == SOLD){
    47             System.out.println("重复转动不能得到两次糖果");
    48         }else if(state == NO_QUARTZ){
    49             System.out.println("未投币,没有糖果发放");
    50         }else if(state == SOLD_OUT){
    51             System.out.println("糖果售罄,没有糖果发放");
    52         }else if(state == HAS_QUARTZ){
    53             System.out.println("已转动曲柄");
    54             state = SOLD;
    55             dispense();
    56         }
    57     }
    58     
    59     //发放糖果
    60     public void dispense(){
    61         if(state == SOLD){
    62             System.out.println("糖果即将发放");
    63             count--;
    64             if(count == 0){
    65                 System.out.println("这是最后一颗糖果,已售罄");
    66                 state == SOLD_OUT;
    67             }else{
    68                 state = NO_QUARTZ;
    69             }
    70         }else if(state == NO_QUARTZ){
    71             System.out.println("需要先付钱");
    72         }else if(state == SOLD_OUT){
    73             System.out.println("没有糖果发放");
    74         }else if(state == HAS_QUARTZ){
    75             System.out.println("没有糖果发放");
    76         }
    77     }
    78 }
    代码比较简单,就是每种动作对应一个函数,在每种动作中需要对状态进行逐一判断。如果符合,则需要转换为下一个状态。否则,仅简单打印不符合原由。至少到目前来看,代码是合理的而且正常运作。
    糖果机在发放使用后效果不错,糖果公司为了进一步提高销售量,又提出了一个新的需求:当个赢家!当曲柄转动时,有10%的机率掉下来的是两个糖果。
     
    该来的躲不掉,需求变动!!!
     
    大致思考一下为了实现这个需求需要做的事情:1、需要加一个赢家状态 WINNER(easy) 2、在每个动作上面都要添加这个状态的判断(烦)3、转动曲柄和发放糖果动作逻辑要改(烦),综合一下,觉得加了一个状态实在麻烦,万一加完这个状态以后再有新需求!!!,这个就越来越复杂,,,这时候考虑用状态模式。

    例子

    首先定义一个状态接口State,里面包含了要执行的动作函数。对于上述例子,具体如下:
     1 public interface State {
     2     //投入25分钱
     3     void insertQuartz();
     4     //退回25分钱
     5     void ejectQuartz();
     6     //转动曲柄
     7     void turnCrank();
     8     //发放糖果
     9     void dispense();
    10 }
    接着每个状态定义为一个类,并继承State接口。类图如下:
    这样在每一个状态类里面分别实现这些东西,这里贴出一个类的具体实现:
     1 /**
     2  * 出售状态
     3  */
     4 public class SoldState implements State{
     5     private CandyMachine candyMachine;
     6     public SoldState(CandyMachine candyMachine) {
     7         this.candyMachine = candyMachine;
     8     }
     9     @Override
    10     public void insertQuartz() {
    11         System.out.println("请等待,正在发放糖果");
    12     }
    13     @Override
    14     public void ejectQuartz() {
    15         System.out.println("已经转动曲柄,不能退钱");
    16     }
    17     @Override
    18     public void turnCrank() {
    19         System.out.println("重复转动不能得到两次糖果");
    20     }
    21     @Override
    22     public void dispense() {
    23         candyMachine.releaseBall();
    24         if(candyMachine.getCount() > 0){
    25             candyMachine.setState(candyMachine.getNoQuartzState());
    26         }else{
    27             candyMachine.setState(candyMachine.getSoldOutState());
    28         }
    29     }
    30 }
    发放糖果类这样写:
     1 public class CandyMachine {
     2     private int count = 0;
     3     private State soldState;
     4     private State noQuartzState;
     5     private State hasQuartzState;
     6     private State soldOutState;
     7     private State state = soldOutState;
     8     public CandyMachine(int count) {
     9         this.count = count;
    10         this.soldState = new SoldState(this);
    11         this.noQuartzState = new SoldOutState(this);
    12         this.hasQuartzState = new HasQuartzState(this);
    13         this.soldOutState = new NoQuartzState(this);
    14         if(count > 0){
    15             state = noQuartzState;
    16         }
    17     }
    18     public void insertQuartz() {
    19         state.insertQuartz();
    20     }
    21     public void ejectQuartz() {
    22         state.ejectQuartz();
    23     }
    24     public void turnCrank() {
    25         state.turnCrank();
    26         state.dispense();
    27     }
    28     //发放糖果
    29     public void releaseBall(){
    30         System.out.println("糖果即将发放");
    31         if(count != 0){
    32             count--;
    33         }
    34     }
    35 }
    这样的话,在状态类中每一种动作是明确的。就是对该种状态进行处理,所以可以看到,关于状态的判断的if...else就没有了。
    再来看对于上面提到的需求变更的问题。当个赢家!当曲柄转动时,有10%的机率掉下来的是两个糖果。
    新增一个赢家状态类。
     1 public class WinnerState implements State {
     2     private CandyMachine candyMachine;
     3     public WinnerState(CandyMachine candyMachine) {
     4         this.candyMachine = candyMachine;
     5     }
     6     @Override
     7     public void insertQuartz() {}
     8     @Override
     9     public void ejectQuartz() {}
    10     @Override
    11     public void turnCrank() {}
    12     @Override
    13     public void dispense() {
    14         candyMachine.releaseBall();
    15         //这里处理逻辑。。。
    16     }
    17 }
    到这里就差不多了。回想一下,这里其实就是两种思路。第一种是从四种动作入手,在每种动作中对每种状态进行判断。第二种是从四种状态的角度去看,每种状态要处理四种动作。动作不再需要状态判断,因为在一个状态类中时状态是确定的。第二种方式多了几个状态类,但更好扩展,以后有别的状态的时候只需要新增一个状态就好了。而且状态类中的动作函数不会太复杂,以后有改动时会比较好改。
    还有一点需要注意:这里的状态只是状态模式的一种应用(不限于是状态),也就是说,类似于这种判断的场景都可以使用。例如:实现计算器的符号(+-*/)也可以用这种模式。即每种符号一种状态。总的来说也就是这种需要 多个分支条件的 都可以用状态模式。

    状态模式

    • 状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。
    1、对象在内部对象改变时改变它的行为:拿糖果机的例子来说,就是每个状态是一个状态类,当糖果机的状态不同时(例如NO_QUAETZ、HAS_QUARTZ),那么同一个动作的行为也是不同的。
    2、对象看起来好像修改了它的类:这个就是对于State接口,通过引用不同的实现类看起来是不同的类实例化来实现的一样。
     
    状态模式的类图如下
    这个Context拥有一些状态,在例子中就是CandyMachine。Context中提供对状态的操作。像setState()。
    State是一个接口,定义了状态类的一些动作。ConcreteStateA和ConcreteStateB是具体的状态类。状态类中实现动作在该状态下的操作。

    注意

    1、在每个具体状态类中引用了一个Context,并通过构造函数实例Context,把Context实例引进来就可以操作状态转化以及状态之间共有的部分,例如例子中的糖果机中糖果数量(count)
    2、状态流程运转是这样的:初始时(有糖果)处于NO_QUATRZ状态。当投入钱币时,调用insertQuartz()方法,这时候state的引用是NoQuartzState类的引用。然后在insertQuartz()成功之后状态转换为HAS_QUARTZ状态。这时候state的引用就是HasQuartzState类的引用了。当再调用enjectQuartz()方法时进入的是HasQuartzState类里面的enjectQuartz()方法。以此类推,,,
  • 相关阅读:
    好用的绘图工具推荐-processon
    前台获取到的后台json对象取值时undefined的解决方法
    axios怎么引用?
    postman发送post请求,后端无法接收到参数
    bcrypt加密算法
    mongoose创建model名字时,为什么集合名会自动加s?
    《遇见未知的自己》 张德芬
    《把时间当做朋友》笔记摘要
    精英与普通人的区别
    金刚经全文
  • 原文地址:https://www.cnblogs.com/uodut/p/6953765.html
Copyright © 2020-2023  润新知