設計模式 狀態模式 以自動售貨機爲例

轉載請標明出處:http://blog.csdn.net/lmj623565791/article/details/26350617

狀態模式給了我眼前一亮的感覺啊,值得學習~

先看定義:允許對象在內部狀態改變時改變它的行爲,對象看起來好像修改了它的類。定義又開始模糊了,理一下,當對象的內部狀態改變時,它的行爲跟隨狀態的改變而改變了,看起來好像重新初始化了一個類似的。

下面使用個例子來說明狀態模式的用法,現在有個自動售貨機的代碼需要我們來寫,狀態圖如下:


分析一個這個狀態圖:

a、包含4個狀態(我們使用4個int型常量來表示)

b、包含3個暴露在外的方法(投幣、退幣、轉動手柄)

c、我們需要處理每個狀態下,用戶都可以觸發這三個動作。

下面我們根據分析的結果,寫出代碼:

  1. package com.zhy.pattern.status;  
  2.   
  3. /** 
  4.  * 自動售貨機 
  5.  *  
  6.  * @author zhy 
  7.  *  
  8.  */  
  9. public class VendingMachine  
  10. {  
  11.   
  12.     /** 
  13.      * 已投幣 
  14.      */  
  15.     private final static int HAS_MONEY = 0;  
  16.     /** 
  17.      * 未投幣 
  18.      */  
  19.     private final static int NO_MONEY = 1;  
  20.     /** 
  21.      * 售出商品 
  22.      */  
  23.     private final static int SOLD = 2;  
  24.     /** 
  25.      * 商品售罄 
  26.      */  
  27.     private final static int SOLD_OUT = 3;  
  28.   
  29.     private int currentStatus = NO_MONEY;  
  30.     /** 
  31.      * 商品數量 
  32.      */  
  33.     private int count = 0;  
  34.   
  35.     public VendingMachine(int count)  
  36.     {  
  37.         this.count = count;  
  38.         if (count > 0)  
  39.         {  
  40.             currentStatus = NO_MONEY;  
  41.         }  
  42.     }  
  43.   
  44.     /** 
  45.      * 投入硬幣,任何狀態用戶都可能投幣 
  46.      */  
  47.     public void insertMoney()  
  48.     {  
  49.         switch (currentStatus)  
  50.         {  
  51.         case NO_MONEY:  
  52.             currentStatus = HAS_MONEY;  
  53.             System.out.println("成功投入硬幣");  
  54.             break;  
  55.         case HAS_MONEY:  
  56.             System.out.println("已經有硬幣,無需投幣");  
  57.             break;  
  58.         case SOLD:  
  59.             System.out.println("請稍等...");  
  60.             break;  
  61.         case SOLD_OUT:  
  62.             System.out.println("商品已經售罄,請勿投幣");  
  63.             break;  
  64.   
  65.         }  
  66.     }  
  67.   
  68.     /** 
  69.      * 退幣,任何狀態用戶都可能退幣 
  70.      */  
  71.     public void backMoney()  
  72.     {  
  73.         switch (currentStatus)  
  74.         {  
  75.         case NO_MONEY:  
  76.             System.out.println("您未投入硬幣");  
  77.             break;  
  78.         case HAS_MONEY:  
  79.             currentStatus = NO_MONEY;  
  80.             System.out.println("退幣成功");  
  81.             break;  
  82.         case SOLD:  
  83.             System.out.println("您已經買了糖果...");  
  84.             break;  
  85.         case SOLD_OUT:  
  86.             System.out.println("您未投幣...");  
  87.             break;  
  88.         }  
  89.     }  
  90.   
  91.     /** 
  92.      * 轉動手柄購買,任何狀態用戶都可能轉動手柄 
  93.      */  
  94.     public void turnCrank()  
  95.     {  
  96.         switch (currentStatus)  
  97.         {  
  98.         case NO_MONEY:  
  99.             System.out.println("請先投入硬幣");  
  100.             break;  
  101.         case HAS_MONEY:  
  102.             System.out.println("正在出商品....");  
  103.             currentStatus = SOLD;  
  104.             dispense();  
  105.             break;  
  106.         case SOLD:  
  107.             System.out.println("連續轉動也沒用...");  
  108.             break;  
  109.         case SOLD_OUT:  
  110.             System.out.println("商品已經售罄");  
  111.             break;  
  112.   
  113.         }  
  114.     }  
  115.   
  116.     /** 
  117.      * 發放商品 
  118.      */  
  119.     private void dispense()  
  120.     {  
  121.   
  122.         switch (currentStatus)  
  123.         {  
  124.         case NO_MONEY:  
  125.         case HAS_MONEY:  
  126.         case SOLD_OUT:  
  127.             throw new IllegalStateException("非法的狀態...");  
  128.         case SOLD:  
  129.             count--;  
  130.             System.out.println("發出商品...");  
  131.             if (count == 0)  
  132.             {  
  133.                 System.out.println("商品售罄");  
  134.                 currentStatus = SOLD_OUT;  
  135.             } else  
  136.             {  
  137.                 currentStatus = NO_MONEY;  
  138.             }  
  139.             break;  
  140.   
  141.         }  
  142.   
  143.     }  
  144. }  

針對用戶的每個動作,我們考慮了在任何狀態下發生,並做了一定處理。下面進行一些測試:
  1. package com.zhy.pattern.status;  
  2.   
  3. public class TestTra  
  4. {  
  5.     public static void main(String[] args)  
  6.     {  
  7.         VendingMachine machine = new VendingMachine(10);  
  8.         machine.insertMoney();  
  9.         machine.backMoney();  
  10.   
  11.         System.out.println("-----------");  
  12.   
  13.         machine.insertMoney();  
  14.         machine.turnCrank();  
  15.           
  16.         System.out.println("----------壓力測試-----");  
  17.         machine.insertMoney();  
  18.         machine.insertMoney();  
  19.         machine.turnCrank();  
  20.         machine.turnCrank();  
  21.         machine.backMoney();  
  22.         machine.turnCrank();  
  23.   
  24.     }  
  25. }  
輸出結果:
  1. 成功投入硬幣  
  2. 退幣成功  
  3. -----------  
  4. 成功投入硬幣  
  5. 正在出商品....  
  6. 發出商品...  
  7. ----------壓力測試-----  
  8. 成功投入硬幣  
  9. 已經有硬幣,無需投幣  
  10. 正在出商品....  
  11. 發出商品...  
  12. 請先投入硬幣  
  13. 您未投入硬幣  
  14. 請先投入硬幣  
感覺還是不錯的,基本實現了功能,但是有些事情是不可避免的,那就是需求的變化,現在爲了提升銷量,當用戶每次轉動手柄買商品的時候,有10%的機率贈送一瓶。

現在的狀態圖發生了變化,當用戶轉動手柄時,可能會達到一箇中獎的狀態:圖如下:

如果在我們剛寫的代碼上直接添加,則需要在每個動作的switch中添加判斷條件,且非常容易出錯。所以現在我們要考慮重新設計我們的代碼,我們考慮把每個狀態寫狀態類,負責實現在對應動作下的行爲,然後自動售貨機在不能的狀態間切換:

下面開始重構,我們現在有5種狀態,對應4個動作(投幣、退幣、轉動手柄、發出商品),下面首先定義一個狀態的超類型:

  1. package com.zhy.pattern.status.b;  
  2.   
  3. /** 
  4.  * 狀態的接口 
  5.  * @author zhy 
  6.  * 
  7.  */  
  8. public interface State  
  9. {  
  10.     /** 
  11.      * 放錢 
  12.      */  
  13.     public void insertMoney();  
  14.     /** 
  15.      * 退錢 
  16.      */  
  17.     public void backMoney();  
  18.     /** 
  19.      * 轉動曲柄 
  20.      */  
  21.     public void turnCrank();  
  22.     /** 
  23.      * 出商品 
  24.      */  
  25.     public void dispense();  
  26. }  

然後分別是每個狀態的實現:
  1. package com.zhy.pattern.status.b;  
  2.   
  3. /** 
  4.  * 沒錢的狀態 
  5.  * @author zhy 
  6.  * 
  7.  */  
  8. public class NoMoneyState implements State  
  9. {  
  10.   
  11.     private VendingMachine machine;  
  12.   
  13.     public NoMoneyState(VendingMachine machine)  
  14.     {  
  15.         this.machine = machine;  
  16.           
  17.     }  
  18.       
  19.     @Override  
  20.     public void insertMoney()  
  21.     {  
  22.         System.out.println("投幣成功");  
  23.         machine.setState(machine.getHasMoneyState());  
  24.     }  
  25.   
  26.     @Override  
  27.     public void backMoney()  
  28.     {  
  29.         System.out.println("您未投幣,想退錢?...");  
  30.     }  
  31.   
  32.     @Override  
  33.     public void turnCrank()  
  34.     {  
  35.         System.out.println("您未投幣,想拿東西麼?...");  
  36.     }  
  37.   
  38.     @Override  
  39.     public void dispense()  
  40.     {  
  41.         throw new IllegalStateException("非法狀態!");  
  42.     }  
  43.   
  44. }  

  1. package com.zhy.pattern.status.b;  
  2.   
  3. import java.util.Random;  
  4.   
  5. /** 
  6.  * 已投入錢的狀態 
  7.  *  
  8.  * @author zhy 
  9.  *  
  10.  */  
  11. public class HasMoneyState implements State  
  12. {  
  13.   
  14.     private VendingMachine machine;  
  15.     private Random random = new Random();  
  16.   
  17.     public HasMoneyState(VendingMachine machine)  
  18.     {  
  19.         this.machine = machine;  
  20.     }  
  21.   
  22.     @Override  
  23.     public void insertMoney()  
  24.     {  
  25.         System.out.println("您已經投過幣了,無需再投....");  
  26.     }  
  27.   
  28.     @Override  
  29.     public void backMoney()  
  30.     {  
  31.         System.out.println("退幣成功");  
  32.   
  33.         machine.setState(machine.getNoMoneyState());  
  34.     }  
  35.   
  36.     @Override  
  37.     public void turnCrank()  
  38.     {  
  39.         System.out.println("你轉動了手柄");  
  40.         int winner = random.nextInt(10);  
  41.         if (winner == 0 && machine.getCount() > 1)  
  42.         {  
  43.             machine.setState(machine.getWinnerState());  
  44.         } else  
  45.         {  
  46.             machine.setState(machine.getSoldState());  
  47.         }  
  48.     }  
  49.   
  50.     @Override  
  51.     public void dispense()  
  52.     {  
  53.         throw new IllegalStateException("非法狀態!");  
  54.     }  
  55.   
  56. }  

  1. package com.zhy.pattern.status.b;  
  2.   
  3. /** 
  4.  * 售罄的狀態 
  5.  *  
  6.  * @author zhy 
  7.  *  
  8.  */  
  9. public class SoldOutState implements State  
  10. {  
  11.   
  12.     private VendingMachine machine;  
  13.   
  14.     public SoldOutState(VendingMachine machine)  
  15.     {  
  16.         this.machine = machine;  
  17.     }  
  18.   
  19.     @Override  
  20.     public void insertMoney()  
  21.     {  
  22.         System.out.println("投幣失敗,商品已售罄");  
  23.     }  
  24.   
  25.     @Override  
  26.     public void backMoney()  
  27.     {  
  28.         System.out.println("您未投幣,想退錢麼?...");  
  29.     }  
  30.   
  31.     @Override  
  32.     public void turnCrank()  
  33.     {  
  34.         System.out.println("商品售罄,轉動手柄也木有用");  
  35.     }  
  36.   
  37.     @Override  
  38.     public void dispense()  
  39.     {  
  40.         throw new IllegalStateException("非法狀態!");  
  41.     }  
  42.   
  43. }  

  1. package com.zhy.pattern.status.b;  
  2.   
  3. /** 
  4.  * 準備出商品的狀態,該狀態下,不會有任何用戶的操作 
  5.  *  
  6.  * @author zhy 
  7.  *  
  8.  */  
  9. public class SoldState implements State  
  10. {  
  11.   
  12.     private VendingMachine machine;  
  13.   
  14.     public SoldState(VendingMachine machine)  
  15.     {  
  16.         this.machine = machine;  
  17.     }  
  18.   
  19.     @Override  
  20.     public void insertMoney()  
  21.     {  
  22.         System.out.println("正在出貨,請勿投幣");  
  23.     }  
  24.   
  25.     @Override  
  26.     public void backMoney()  
  27.     {  
  28.         System.out.println("正在出貨,沒有可退的錢");  
  29.     }  
  30.   
  31.     @Override  
  32.     public void turnCrank()  
  33.     {  
  34.         System.out.println("正在出貨,請勿重複轉動手柄");  
  35.     }  
  36.   
  37.     @Override  
  38.     public void dispense()  
  39.     {  
  40.         machine.dispense();  
  41.         if (machine.getCount() > 0)  
  42.         {  
  43.             machine.setState(machine.getNoMoneyState());  
  44.         } else  
  45.         {  
  46.             System.out.println("商品已經售罄");  
  47.             machine.setState(machine.getSoldOutState());  
  48.         }  
  49.     }  
  50. }  
  1. package com.zhy.pattern.status.b;  
  2.   
  3. /** 
  4.  * 中獎的狀態,該狀態下不會有任何用戶的操作 
  5.  *  
  6.  * @author zhy 
  7.  *  
  8.  */  
  9. public class WinnerState implements State  
  10. {  
  11.   
  12.     private VendingMachine machine;  
  13.   
  14.     public WinnerState(VendingMachine machine)  
  15.     {  
  16.         this.machine = machine;  
  17.     }  
  18.   
  19.     @Override  
  20.     public void insertMoney()  
  21.     {  
  22.         throw new IllegalStateException("非法狀態");  
  23.     }  
  24.   
  25.     @Override  
  26.     public void backMoney()  
  27.     {  
  28.         throw new IllegalStateException("非法狀態");  
  29.     }  
  30.   
  31.     @Override  
  32.     public void turnCrank()  
  33.     {  
  34.         throw new IllegalStateException("非法狀態");  
  35.     }  
  36.   
  37.     @Override  
  38.     public void dispense()  
  39.     {  
  40.         System.out.println("你中獎了,恭喜你,將得到2件商品");  
  41.         machine.dispense();  
  42.   
  43.         if (machine.getCount() == 0)  
  44.         {  
  45.             System.out.println("商品已經售罄");  
  46.             machine.setState(machine.getSoldOutState());  
  47.         } else  
  48.         {  
  49.             machine.dispense();  
  50.             if (machine.getCount() > 0)  
  51.             {  
  52.                 machine.setState(machine.getNoMoneyState());  
  53.             } else  
  54.             {  
  55.                 System.out.println("商品已經售罄");  
  56.                 machine.setState(machine.getSoldOutState());  
  57.             }  
  58.               
  59.         }  
  60.   
  61.     }  
  62.   
  63. }  


最後是自動售貨機的代碼:
  1. package com.zhy.pattern.status.b;  
  2.   
  3. /** 
  4.  * 自動售貨機 
  5.  *  
  6.  * @author zhy 
  7.  *  
  8.  */  
  9. public class VendingMachine  
  10. {  
  11.     private State noMoneyState;  
  12.     private State hasMoneyState;  
  13.     private State soldState;  
  14.     private State soldOutState;  
  15.     private State winnerState ;   
  16.   
  17.     private int count = 0;  
  18.     private State currentState = noMoneyState;  
  19.   
  20.     public VendingMachine(int count)  
  21.     {  
  22.         noMoneyState = new NoMoneyState(this);  
  23.         hasMoneyState = new HasMoneyState(this);  
  24.         soldState = new SoldState(this);  
  25.         soldOutState = new SoldOutState(this);  
  26.         winnerState = new WinnerState(this);  
  27.   
  28.         if (count > 0)  
  29.         {  
  30.             this.count = count;  
  31.             currentState = noMoneyState;  
  32.         }  
  33.     }  
  34.   
  35.     public void insertMoney()  
  36.     {  
  37.         currentState.insertMoney();  
  38.     }  
  39.   
  40.     public void backMoney()  
  41.     {  
  42.         currentState.backMoney();  
  43.     }  
  44.   
  45.     public void turnCrank()  
  46.     {  
  47.         currentState.turnCrank();  
  48.         if (currentState == soldState || currentState == winnerState)  
  49.             currentState.dispense();  
  50.     }  
  51.   
  52.     public void dispense()  
  53.     {  
  54.         System.out.println("發出一件商品...");  
  55.         if (count != 0)  
  56.         {  
  57.             count -= 1;  
  58.         }  
  59.     }  
  60.   
  61.     public void setState(State state)  
  62.     {  
  63.         this.currentState = state;  
  64.     }  
  65.   
  66.     //getter setter omitted ...  
  67.   
  68. }  

可以看到,我們現在把每個狀態對應於動作的行爲局部化到了狀態自己的類中實現,不僅增加了擴展性而且使代碼的閱讀性大幅度的提高。以後再添加狀態,只需要針對新添加的狀態的實現類,並在自動售貨機中添加此狀態即可。

下面進行一些測試:

  1. package com.zhy.pattern.status.b;  
  2.   
  3. public class Test  
  4. {  
  5.     public static void main(String[] args)  
  6.     {  
  7.         VendingMachine machine = new VendingMachine(10);  
  8.         machine.insertMoney();  
  9.         machine.backMoney();  
  10.   
  11.         System.out.println("----我要中獎----");  
  12.   
  13.         machine.insertMoney();  
  14.         machine.turnCrank();  
  15.         machine.insertMoney();  
  16.         machine.turnCrank();  
  17.         machine.insertMoney();  
  18.         machine.turnCrank();  
  19.         machine.insertMoney();  
  20.         machine.turnCrank();  
  21.         machine.insertMoney();  
  22.         machine.turnCrank();  
  23.         machine.insertMoney();  
  24.         machine.turnCrank();  
  25.         machine.insertMoney();  
  26.         machine.turnCrank();  
  27.   
  28.         System.out.println("-------壓力測試------");  
  29.   
  30.         machine.insertMoney();  
  31.         machine.backMoney();  
  32.         machine.backMoney();  
  33.         machine.turnCrank();// 無效操作  
  34.         machine.turnCrank();// 無效操作  
  35.         machine.backMoney();  
  36.   
  37.     }  
  38. }  

輸出結果:
  1. 投幣成功  
  2. 退幣成功  
  3. ----我要中獎----  
  4. 投幣成功  
  5. 你轉動了手柄  
  6. 發出一件商品...  
  7. 投幣成功  
  8. 你轉動了手柄  
  9. 發出一件商品...  
  10. 投幣成功  
  11. 你轉動了手柄  
  12. 發出一件商品...  
  13. 投幣成功  
  14. 你轉動了手柄  
  15. 發出一件商品...  
  16. 投幣成功  
  17. 你轉動了手柄  
  18. 發出一件商品...  
  19. 投幣成功  
  20. 你轉動了手柄  
  21. 發出一件商品...  
  22. 投幣成功  
  23. 你轉動了手柄  
  24. 你中獎了,恭喜你,將得到2件商品  
  25. 發出一件商品...  
  26. 發出一件商品...  
  27. -------壓力測試------  
  28. 投幣成功  
  29. 退幣成功  
  30. 您未投幣,想退錢?...  
  31. 您未投幣,想拿東西麼?...  
  32. 您未投幣,想拿東西麼?...  
  33. 您未投幣,想退錢?...  

恭喜你,又學會了一個設計模式,狀態模式。最後看下狀態模式的類圖
發佈了66 篇原創文章 · 獲贊 13 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章