zoukankan      html  css  js  c++  java
  • 状态模式

    使用场景

    业务中比较多的状态,不同状态下需要做的事情不同。这样,每个步骤中都需要判断一下当前属于什么状态,状态过滤完成以后对相应的状态作出处理。当前状态执行完成以后,可能需要根据条件进入下一个状态(可能是下一个状态,可能是上一个状态,可能是终止或者回到初始状态等)。这种逻辑判断完以后一般都会出现比较多的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()方法。以此类推,,,
  • 相关阅读:
    SQL Server 中的事务与事务隔离级别以及如何理解脏读, 未提交读,不可重复读和幻读产生的过程和原因
    微软BI 之SSIS 系列
    微软BI 之SSIS 系列
    微软BI 之SSIS 系列
    微软BI 之SSIS 系列
    微软BI 之SSIS 系列
    微软BI 之SSAS 系列
    微软BI 之SSRS 系列
    微软BI 之SSRS 系列
    配置 SQL Server Email 发送以及 Job 的 Notification通知功能
  • 原文地址:https://www.cnblogs.com/uodut/p/6953765.html
Copyright © 2011-2022 走看看