iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 17
1

販賣機

相信大家在外面都有用過飲料販賣機, 假設有一台小王牌飲料販賣機, 販賣機上只有一個販賣按鈕, 一個顯示液晶螢幕, 一個投幣孔, 一個退幣洞和一個出貨洞. 目前販賣機只賣一種飲料20元, 庫存2瓶.

  • 沒有投幣時, 當販賣機飲料還有庫存的時候, 販賣機投幣孔開啟, 販賣按鈕燈不會亮, 螢幕上顯示 "庫存N 瓶, 請投幣購買"
  • 當販賣機賣完飲料時, 販賣按鈕燈不會亮, 投幣孔關閉, 螢幕上顯示 "飲料已售完"
  • 當你投幣硬幣時, 但投幣總金額不夠, 販賣按鈕燈不會亮, 投幣孔持續開放, 螢幕上顯示 "已投N 元,請繼續投幣"
  • 當你投幣到足夠的金額時, 販賣按鈕的燈才會亮起, 投幣孔也同時關閉, 螢幕上顯示 "已投N 元, 請按下購買"
  • 當你按下販賣按鈕, 如果販賣機還有庫存, 就回到第一點
  • 當投幣孔開啟是投幣進去, 投進去的硬幣不會馬上從退幣孔出來. 反之投幣孔關閉是投幣進去的銅板, 馬上會從退幣孔出來

假設小張投了10元. 此時販賣按鈕燈不會亮起, 投幣孔持續開放, 螢幕上顯示 "已投10 元, 請繼續投幣".

小張再投5元, 此時販賣按鈕燈不會亮起, 投幣孔持續開放, 螢幕上顯示 "已投15 元, 請繼續投幣".

小張再投10元, 此時販賣按鈕燈亮起, 投幣孔關閉, 螢幕上顯示 "已投25 元, 請按下購買".

當小張按下販賣按鈕, 販賣機退5元, 販賣機此時剩下1瓶, 投幣孔開啟, 販賣按鈕燈不會亮, 螢幕上顯示 "庫存1 瓶, 請投幣購買".

你要設計販賣機物件(VendingMachine), 販賣機物件提供

以下屬性

  • 液晶螢幕 -- ledScreen: string; --> 用來顯示內容
  • 販賣按鈕上的燈 -- vendingLed: boolean; -->true:亮起 false:關閉

以下功能

  • 投幣孔 -- insertMoney(money: number): number; --> 輸入是投幣金額, 返回值表示退幣孔退幣的金額
  • 販賣按鈕 -- buyButton(): [boolean, number]; --> 返回值表示販賣是否成功, 要退還多少錢
  • 取消按鈕 -- cancel(): number; --> 返回值表示退幣孔退幣的金額

小明拿到這個需求, 馬上設計了以下物件

class VendingMachine {
   private _inventory: number;   //販賣機裡面剩餘飲料的庫存數量
   private _price: number;       //飲料價錢
   private _amount: number = 0;      //已投幣多少金額
   private _canInsertMoney: boolean = true;  //是否能夠投幣
   private _canBuy: boolean = false;

   constructor (inventory: number, price: number) {
      this._inventory = inventory; 
      this._price = price;
      this.ledScreen = `庫存${this._inventory} 瓶, 請投幣購買`;
   }

   ledScreen: string = "";
   vendingLed: boolean = false;

   insertMoney(money: number): number {
      if( !this._canInsertMoney ) {
         return money;
      }

      this._amount += money;

      if( this._amount >= this._price ){
         this.vendingLed = true;
         this.ledScreen = `已投${this._amount} 元, 請按下購買`;
         this._canBuy = true;
         return 0;
      } else {
         this.vendingLed = false;
         this.ledScreen = `已投${this._amount} 元, 請繼續投幣`;
         this._canBuy = false;
         return 0;
      }
   } 

   buyButton(): [boolean, number] {
      if( !this._canBuy ) {
         return [false, 0];
      }
      let refund = this._amount - this._price;
      this.cancel();
      return [true, refund];
   }

   cancel(): number {
      if( this._inventory > 0 ) {
         this.ledScreen = `庫存${this._inventory} 瓶, 請投幣購買`;
         this._canInsertMoney = true;
      } else {
         this.ledScreen = `飲料已售完`; 
         this._canInsertMoney = false;
      }
      let refund = this._amount;
      this._amount = 0;
      return refund;
   }
} 

觀察上面的程式碼, 你會發現這個物件有許多變數狀態, 每一個功能(function) 隨著變數狀態改變而改變function 的行為. 甚至某些公開方法(function) 故意呼叫其它的公開方法(function) 來改變物件內部的狀態.

有時候某些公開方法(function) 內部有許多if else 判斷式, 假設後續要增加新的功能(function), 就可能會持續增加變數狀態用來判斷新功能是否開啟或關閉.

最後程式碼會變得冗長不好維護, 還要擔心每一個功能是否會互相干擾, 例如其他功能的開啟或關閉. 這個時後我們可以用狀態模式將每個狀態視為一個獨立物件, 讓後續需求更動時, 程式碼比較能更好維護.

狀態模式(State Pattern)

分析販賣機的行為, 找出所有狀態, 我們可以把販賣機狀態分類為

  • 販賣中
  • 已售完
  • 投幣中

上一篇
責任鏈模式 - 16
下一篇
狀態模式(State Pattern) - 18
系列文
為什麼世界需要Typescript30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言