iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 19
1
Software Development

為什麼世界需要Typescript系列 第 19

狀態模式(State Pattern) - 19

  • 分享至 

  • xImage
  •  

這個抽象類別(Class) 就是一個共同的狀態類別, 當作為共同的介面, 定義如下

abstract class State {
   abstract insertMoney(money: number): number;
   abstract buyButton(): [boolean, number];
   abstract cancel(): number;
}

將 enum VendingState 中的每一種狀態建立類別物件, 並繼承上面共同狀態類別, 例如 VendingState.Vending 就建立為

class VendingState extends State {
   insertMoney(money: number): number {
   }   
   buyButton(): [boolean, number] {
   }
   cancel(): number {
   }
}

再將先前insertMoney 方法中的 if-else 區塊內的程式碼

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

搬到這個 VendingState 狀態類別的 insertMoney 方法內

class VendingState extends State {
   insertMoney(money: number): number {
      this._amount = money;
      if( this._amount >= this._price ){
         this.vendingLed = true;
         this.ledScreen = `已投${this._amount} 元, 請按下購買`;
      } else {
         this.ledScreen = `已投${this._amount} 元, 請繼續投幣`; 
      }
      this._state = VendingState.InsertingCoins;
      return 0;
   }
}

然後把設定狀態的程式碼, 提取為setCurrentState 方法並搬到 State 類別物件內

this._state = VendingState.InsertingCoins;

上面一行程式碼變成

abstract class State {
   ...
   protected void setCurrentState(State newState) {
      this._state = newState;
   }
}

以此類推, 把先前其他的方法 buyButton(), cancel() 裡面的 if-else 區段程式碼也一併搬到 VendingState 類別物件內

buyButton(): [boolean, number] {
   ...
   if( this._state == VendingState.Vending ) {
      return [false, 0];
   }
   ...
}

cancel(): number {
   ...
   if( this._state == VendingState.Vending ) {
      return 0;
   }
   ...
}

最後 VendingState 類別物件補齊全了 insertMoney, buyButton, cancel 中的程式碼

class VendingState extends State {
   insertMoney(money: number): number {
      this._amount = money;
      if( this._amount >= this._price ){
         this.vendingLed = true;
         this.ledScreen = `已投${this._amount} 元, 請按下購買`;
      } else {
         this.ledScreen = `已投${this._amount} 元, 請繼續投幣`; 
      }
      this.setCurrentState(VendingState.InsertingCoins);
      return 0;
   }

   buyButton(): [boolean, number] {
      return [false, 0];
   }

   cancel(): number {
      return 0;
   }
}

你可以發現到在 VendingState 販賣中狀態裡,

buyButton() 方法只需回傳不允許購買, 退幣孔不會退任何錢出來.
cancel() 也只需設定退幣孔不會退任何錢出來.

每一個方法中需要實踐的程式碼變更少了, 也不用考慮其他狀態的動作了.

同樣的再繼續實作其他的狀態類別物件

class SoldOutState extends State {
   insertMoney(money: number): number {
      return money;
   }
   buyButton(): [boolean, number] {
      return [false, 0];
   }
   cancel(): number {
      return 0;
   }
}

class InsertingCoinsState extends State {
   insertMoney(money: number): number {
      if( this._amount >= this._price ){
         this.vendingLed = true;
         this.ledScreen = `已投${this._amount} 元, 請按下購買`;
         return money;
      } else {
         this.vendingLed = false;
         this._amount += money;
         this.ledScreen = `已投${this._amount} 元, 請繼續投幣`;
         return 0;
      }
   }
   buyButton(): [boolean, number] {
      if( this._amount >= this._price ){
         let refund = this._amount - this._price;
         this._state = VendingState.Vending;
         return [true, refund];
      } else {
         return [false, 0];
      }
   }
   cancel(): number {
      if( this._inventory > 0 ) {
         this.ledScreen = `庫存${this._inventory} 瓶, 請投幣購買`;
         this._state = VendingState.Vending
      } else {
         this.ledScreen = `飲料已售完`; 
         this._state = VendingState.SoldOut;
      }
      let refund = this._amount;
      this._amount = 0;
      return refund;
   }
}

實踐好每一種狀態物件之後, 接下來...


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

尚未有邦友留言

立即登入留言