• 设计模式篇——初探状态模式


    状态模式简介:

    状态模式允许一个对象基于内部状态而拥有不同的行为,这个对象看起来就好像修改了它的类。

    Context将行为委托给当前状态对象。

    把每个状态封装进一个类中,以此来解耦和扩展

    状态装换可以有State类或者Context类来控制

    状态模式通常会导致设计中的类的数目大量增加

    状态类可以被多个Context实例共享。

      假设我么现在有一个糖果机,可以投入“1块钱硬币“ ,转动把手,弹出一颗糖果。也可以不转动把手,把投入的钱币退回=》

      我们可以从这个过程中分析得出,这个过程一共有4个状态=》没有投币,已经投币,发放(出售)糖果,糖果售罄;基于这四个状态,也有四个动作投入钱币,退回钱币,转动把手,发放糖果(这个动作是在糖果机内部完成)。

      基于这个我们创建一个GumballMachine(糖果机)类,糖果机实现了三个行为(投币,退币,摇动把手),构造函数中传入初始放入的糖果数量。糖果机中存在一个State的抽象类(下文中实现),代表所有状态的一个父类。setState方法更新状态。releaseBall方法弹出糖果,GetCount方法返回当前糖果数量。

     1     public class GumballMachine
     2     {
     3         //所有的状态(初始化只用到soldOut状态和noMoneyState状态)
     4         static State soldOutState;  //售罄
     5         static State noMoneyState;  //没有投币
     6         //static State hasMoneyState; //已投币
     7         //static State soldState;     //出售
     8 
     9         private int _count; //糖果数量
    10         private State _state = soldOutState; 
    11         public GumballMachine(int count)
    12         {
    13             _count = count;
    14             soldOutState = new SoldOutState(this);
    15             noMoneyState = new NoMoneyState(this);
    16             //hasMoneyState = new HasMoneyState(this);
    17             //soldState = new SoldState(this);
    18             if (_count > 0)
    19                 _state = noMoneyState;      //糖果数量大于0,初始状态修改为未投币
    20         }
    21         public void insertMoney()           //投币
    22         {
    23             _state.insertMoney();
    24         }
    25         public void ejectMoney()            //退币
    26         {   
    27             _state.ejectMoney();
    28         }
    29 
    30         /// <summary>
    31         /// 转动把手
    32         /// </summary>
    33         public void turnCrank() 
    34         {
    35             _state.turnCrank();         //转动
    36             _state.dispense();          //发放糖果
    37         }          
    38         /// <summary>
    39         /// 更新状态
    40         /// </summary>
    41         /// <param name="state"></param>
    42         public void setState(State state)
    43         {
    44             _state = state;
    45         }
    46         /// <summary>
    47         /// 弹出糖果
    48         /// </summary>
    49         public void releaseBall()
    50         {
    51             Console.WriteLine("**************弹出一颗糖果●**************");
    52             if (_count != 0)
    53                 _count = _count - 1;
    54         }
    55         /// <summary>
    56         /// 返回当前糖果状态
    57         /// </summary>
    58         /// <returns></returns>
    59         public int GetCount()
    60         {
    61             return _count;
    62         }
    63     }
    GumballMachine(糖果机)

      我们基于所有状态抽象出一个父类State,这个父类有四个抽象方法,分别代表四个动作。(PS:这里抽象类或者接口由使用场景选择,抽象类可以让所有子类都初始化一些操作。)

     1     /// <summary>
     2     /// 状态的抽象类,所有状态都要继承自它
     3     /// </summary>
     4     public abstract class State
     5     {
     6         public abstract void insertMoney(); //放入硬币
     7         public abstract void ejectMoney();  //取出硬币
     8         public abstract void turnCrank();   //转动把手
     9         public abstract void dispense();    //发放糖果
    10     }
    State抽象类

      接下来我们实现这四个状态。

     1     /// <summary>
     2     /// 没有投钱
     3     /// </summary>
     4     public class NoMoneyState : State
     5     {
     6         private readonly GumballMachine _GumballMachine;
     7         public NoMoneyState(GumballMachine GumballMachine)
     8         {
     9             _GumballMachine = GumballMachine;
    10         }
    11         public override void dispense()
    12         {
    13             Console.WriteLine("需要先投币才能发放糖果");
    14         }
    15 
    16         public override void ejectMoney()
    17         {
    18             Console.WriteLine("没有投币不能退币");
    19         }
    20 
    21         public override void insertMoney()
    22         {
    23             Console.WriteLine("投入了1块钱,可以转动手柄弹出糖果。");
    24             _GumballMachine.setState(new HasMoneyState(_GumballMachine)); //状态转换
    25         }
    26 
    27         public override void turnCrank()
    28         {
    29             Console.WriteLine("没有投币不能转动");
    30         }
    31     }
    没有投币
     1     /// <summary>
     2     /// 已经投钱
     3     /// </summary>
     4     public class HasMoneyState : State
     5     {
     6         private readonly GumballMachine _GumballMachine;
     7         public HasMoneyState(GumballMachine GumballMachine)
     8         {
     9             _GumballMachine = GumballMachine;
    10         }
    11         public override void insertMoney()
    12         {
    13             Console.WriteLine("你已经投币,不需要重复投币,请等待。。");
    14         }
    15         public override void ejectMoney()
    16         {
    17             Console.WriteLine("退币。。现在机器是未投币");
    18             _GumballMachine.setState(new NoMoneyState(_GumballMachine));
    19         }
    20         public override void turnCrank()
    21         {
    22             Console.WriteLine("摇动把手");
    23             _GumballMachine.setState(new SoldState(_GumballMachine));
    24         }
    25         public override void dispense()
    26         {
    27             Console.WriteLine("发放糖果中(不正确的操作。。。)");
    28         }
    29     }
    已经投币
     1  /// <summary>
     2     /// 发放(出售)糖果
     3     /// </summary>
     4     public class SoldState : State
     5     {
     6         private readonly GumballMachine _GumballMachine;
     7         public SoldState(GumballMachine GumballMachine)
     8         {
     9             _GumballMachine = GumballMachine;
    10         }
    11         public override void dispense()
    12         {
    13             _GumballMachine.releaseBall();  //发放糖果  ,判断糖果数量,修改状态
    14             if (_GumballMachine.GetCount() > 0)
    15                 _GumballMachine.setState(new NoMoneyState(_GumballMachine));
    16             else
    17             {
    18                 Console.WriteLine("糖果售罄");
    19                 _GumballMachine.setState(new SoldOutState(_GumballMachine));
    20             }
    21         }
    22 
    23         public override void ejectMoney()
    24         {
    25             Console.WriteLine("已转动把手,不能再退币(不正确动作)");
    26         }
    27 
    28         public override void insertMoney()
    29         {
    30             Console.WriteLine("正在发放糖果,不要重复投币(不正确动作)");
    31         }
    32 
    33         public override void turnCrank()
    34         {
    35             Console.WriteLine("不要重复转动把手(不正确操作)");
    36         }
    37     }
    发放(出售)糖果
     1     /// <summary>
     2     /// 售罄
     3     /// </summary>
     4     public class SoldOutState : State
     5     {
     6         private readonly GumballMachine _GumballMachine;
     7         public SoldOutState(GumballMachine GumballMachine)
     8         {
     9             _GumballMachine = GumballMachine;
    10         }
    11         public override void dispense()
    12         {
    13             Console.WriteLine("糖果已售罄,不能发放糖果(不正确的操作)");
    14         }
    15 
    16         public override void ejectMoney()
    17         {
    18             Console.WriteLine("请先投币,再退币(不正确的操作)");
    19         }
    20 
    21         public override void insertMoney()
    22         {
    23             Console.WriteLine("糖果已售罄,不要再投币(不正确的操作)");
    24         }
    25 
    26         public override void turnCrank()
    27         {
    28             Console.WriteLine("请先投币,再转动把手(不正确的操作)");
    29         }
    30     }
    售罄

      要注意这四个状态都要继承自State抽象类,它们的构造函数中均需要传入GumballMachine,它们各个动作会使糖果机处于不同的状态。(例如NoMoneyState(未投币)状态下,执行insertMoney动作后,糖果机状态转换为HasMoneyState(已投币状态))。不同状态中存在一些无效的操作,这里我们直接打印了一句话,这里根据使用场景具体分析、处理。

      接下来我们来运行一下=》我们初始化一个糖果机,放入100颗糖果。

      接下来,然我们来看下状态模式的类图。Context上下文中存有一个State的超类,Context将不同的行为委托给具体的状态(没有投币,已经投币,发放(出售)糖果,糖果售罄来实现;State封装Context的一组特定行为。具体的状态根据当前环境以实现不同的效果。

      

      这样做的好处是将State之间的逻辑解耦。例如,我们现在要再添加一个状态:幸运者!这样我们的系统就变成这样,在投币之后,我们将有1/10的概率成为幸运用户,这时候糖果机将给你两颗糖果。此时,我们添加一个状态LuckyState(幸运用户)。

     1     public class LuckyState : State            //是否是幸运
     2     {
     3         private readonly GumballMachine _GumballMachine;
     4         public LuckyState(GumballMachine GumballMachine)
     5         {
     6             _GumballMachine = GumballMachine;
     7         }
     8 
     9         public override void dispense()     
    10         {
    11             Console.WriteLine("你是幸运者!接下来我将给你两颗糖");
    12             _GumballMachine.releaseBall();
    13             if (_GumballMachine.GetCount() == 0)                            //糖果售罄
    14                 _GumballMachine.setState(new SoldOutState(_GumballMachine));
    15             else
    16             {
    17                 _GumballMachine.releaseBall();                              //给出第二颗糖果
    18                 if (_GumballMachine.GetCount() > 0)
    19                     _GumballMachine.setState(new NoMoneyState(_GumballMachine));    //还有糖果,进入未投币状态
    20                 else
    21                     _GumballMachine.setState(new SoldOutState(_GumballMachine));
    22 
    23             }
    24         }
    25 
    26         public override void ejectMoney()
    27         {
    28             Console.WriteLine("退币");
    29             _GumballMachine.setState(new NoMoneyState(_GumballMachine));
    30         }
    31 
    32         public override void insertMoney()
    33         {
    34             Console.WriteLine("已投币,不需要再次投币(不正确的操作)");
    35         }
    36 
    37         public override void turnCrank()
    38         {
    39             Console.WriteLine("你是幸运者,不需要摇动把手了,直接给你两颗糖(不正确的操作)");
    40         }
    41     }
    幸运用户

      我们还要修改我们的HasMoneyState,当成为幸运用户的时候,糖果机变为另外一个状态。我们来执行下=》

       这样我们就很容易的通过添加状态类来扩展我们的糖果机。

    最后总结一下:

      例子中的糖果机(GumballMachine)就是我们的上下文对象,然后我们的不同的状态变化时候,糖果机(GumballMachine)就会拥有不同的状态,不同的状态下会使GumballMachine有不同的行为(这样看来我们就把我们的Context上下文对象委托给了当前的状态对象),我们每一个状态都有一个单独的类,这些状态类都继承自State抽象类,所以我们可以随意的替换它们。我们要扩展一个状态也只需要添加一个继承自State的状态类。

      同时,这样做的不好处就是我们的状态类会变得很多。

  • 相关阅读:
    CS academy Binary Flips(dp)
    [POJ 1637] Sightseeing tour(网络流)
    Codeforces 346D Robot Control(01BFS)
    BZOJ 2069: [POI2004]ZAW(Dijkstra + 二进制拆分)
    驱动之SPI,UART,I2C的介绍与应用20170118
    USB驱动之CDC类的介绍与应用20160905
    uCOS-II之移植20160823
    java之面向对象20160818
    Java之基础20160806
    Android之框架20160721
  • 原文地址:https://www.cnblogs.com/liumengchen-boke/p/8270221.html
Copyright © 2020-2023  润新知