通俗易懂設計模式解析——狀態模式

  • 2019 年 10 月 14 日
  • 筆記

前言

  今天我們講的是狀態模式【State Pattern】、這個名字咋一看不好理解,但是仔細一想還是比較容易的。狀態模式重點關注的是狀態。狀態又牽扯著什麼呢?房屋的狀態暫且可以分為出租、簽訂合約、退房。那麼出租對應的是什麼呢?出租狀態代表可以租房。可以租房是一個行為了。所以不難理解的是狀態模式關注的是狀態的改變與行為的變化。

狀態模式介紹

一、來由

  在軟體系統中,經常狀態的改變影響著行為的變化。例如房屋狀態是出租既可以租房、出售既可以買賣房、不租售意味不可操作。那麼如何避免對象操作和狀態轉換之間出現緊耦合呢?狀態模式將每種狀態對應的行為抽象出來成為單獨新的對象,這樣狀態的變化不再依賴於對象內部的行為正解決了此問題。

二、意圖

  允許對象在內部狀態發生改變時改變它的行為,對象看起來好像修改了它的類。

三、案例圖

 

四、狀態模式程式碼示例

我們看下案例圖中主要三個部分:

環境角色:包含保留了一個具體狀態的實例、給出當前狀態及調用方法。

抽象狀態:定義介面、封裝一個狀態相對應的行為方法。

具體狀態:實現具體狀態對應的的具體對應行為。

我們繼續看這個房屋的案例,針對房屋我們整理這麼一個案例,租房然後簽訂合約。合約半年內退房無押金。租房時間達到半年退房可得押金。我們看下程式碼實現吧:

 

namespace State_Pattern  {      /// <summary>      /// 房屋對象類      /// </summary>    public  class StatePattern      {          /// <summary>          /// 房屋Id          /// </summary>          public int Id { get; set; }          /// <summary>          /// 房屋名稱          /// </summary>          public string Name { get; set; }          /// <summary>          /// 租房時間/月          /// </summary>          public int Time { get; set; }          /// <summary>          /// 房屋狀態          /// </summary>          public HouseState State { get; set; }          /// <summary>          /// 是否退押金          /// </summary>          public bool IsDeposit { get; set; }      }        /// <summary>      /// 房屋出租狀態枚舉      /// </summary>      public enum HouseState      {          [Description("出租")]          Lease =1,          [Description("簽訂合約")]          Leaseed = 2,          [Description("退房")]          Deposit = 3,      }        /// <summary>      /// 環境角色      /// </summary>      public class Environmental      {          public State _state;            /// <summary>          /// 初始化房屋狀態          /// </summary>          public Environmental()          {              this._state = new LeaseState();          }            public StatePattern _statePattern { get; set; }            /// <summary>          /// 獲取房屋對象          /// </summary>          /// <param name="statePattern"></param>          public void GetStatePattern(StatePattern statePattern,State state=null)          {              _statePattern = statePattern;              if (state!=null)              {                  _state =  state;              }          }            /// <summary>          /// 更改狀態方法          /// </summary>          /// <param name="state"></param>          public void SetState(State state)          {              _state = state;          }            public void Show()          {              if (this._statePattern!=null)              {                  _state.Handle(this);              }              else              {                  Console.WriteLine("無可操作房屋!");              }          }      }        /// <summary>      /// 抽象狀態介面      /// </summary>      public interface  State      {          void Handle(Environmental environmental);      }        /// <summary>      /// 出租狀態      /// </summary>      public class LeaseState : State      {          public void Handle(Environmental environmental)          {                //房屋出租              if (environmental._statePattern.State==HouseState.Lease)              {                  Console.WriteLine($"{environmental._statePattern.Name}房屋正在出租!");                  Console.WriteLine("如果覺得可以的話就簽訂租房合約!");                  environmental.SetState(new LeaseedState());                  environmental.Show();              }          }      }        /// <summary>      /// 簽訂合約狀態      /// </summary>      public class LeaseedState : State      {          public void Handle(Environmental environmental)          {                  //後期辦理退房手續              if (environmental._statePattern.State == HouseState.Lease)              {                  Console.WriteLine($"{environmental._statePattern.Name}簽訂租房合約!");                  environmental._statePattern.State = HouseState.Leaseed;                  environmental._statePattern.Time = 1;                  environmental.SetState(new DepositState());                  environmental.Show();              }          }      }            /// <summary>      /// 退房有押金狀態      /// </summary>      public class DepositState : State      {          public void Handle(Environmental environmental)          {              environmental._statePattern.IsDeposit = true;              if (environmental._statePattern.State == HouseState.Leaseed && environmental._statePattern.Time < 6)              {                  Console.WriteLine($"{environmental._statePattern.Name}如果現在退房的話是不能退押金的!");                  environmental._statePattern.IsDeposit = false;              }              else                  Console.WriteLine($"{environmental._statePattern.Name}如果現在退房的話是可以退押金的!");              Console.WriteLine("考慮是否退房!");          }      }  }

 

namespace State_Pattern  {      class Program      {          static void Main(string[] args)          {              //初始化房源資訊              List<StatePattern> statePatterns = new List<StatePattern>();              statePatterns.Add(new StatePattern {Id=1,Name="房屋一",State=HouseState.Lease });                Environmental environmental = new Environmental();              //房屋一出租              environmental.GetStatePattern(statePatterns.Where(x=>x.Id==1).FirstOrDefault());              environmental.Show();                //時間大於半年可退押金              statePatterns[0].Time = 7;              environmental.Show();              }      }  }

  在上面的程式碼運行之後房屋一的狀態在其對象內部發送了改變,從而行為也發送了變化。剛開始的正在出租改變成了簽訂合約出租之後。行為變化也從簽訂合約轉變成了退房操作。 

使用場景及優缺點

一、使用場景

1、行為隨著狀態改變而改變的場景。

2、條件或分支語句的替代者。

二、優點

1、封裝了狀態及行為的轉換規則。

2、在編寫錢枚舉出可能的狀態,確定狀態的種類。

3、方便狀態的增加。只需要改變狀態即可改變對象的行為。擴展性好。

4、可以多個環境一起共享一個狀態對象,減少了對象個數。

三、缺點

1、狀態模式會增加系統類和對象的個數

2、對開閉原則不友好。增加狀態需要對那些負責狀態轉換的程式碼進行修改。否則的話無法轉換到最新的狀態。

3、狀態模式的結構和實現都比較複雜,使用不當容易造成程式碼混亂及難理解。

總結

  狀態模式到這裡介紹完了,狀態模式模式注重的狀態在內部的改變自動改變其行為。對象看起來好像改變了它的類一樣。抓住重點實現。第一個是狀態的變化。第二個是狀態變化引起的行為變化。第三個是在狀態內部改變的。把狀態的轉換邏輯和狀態對象放在一起。繼而替換一個龐大的語句條件。


     時間抓起來說是金子,抓不住就是流水。

  C#設計模式系列目錄

     歡迎大家掃描下方二維碼,和我一起踏上設計模式的闖關之路吧!

  

Tags: