說到狀態模式,顧名思義,應該就是跟狀態相關的設計模式了,不過,咱們仍是跟前面同樣,先無論狀態模式是個什麼東西,先從一個小小的例子出發,看看狀態模式能爲咱們解決什麼問題。git
如今須要實現一個交通燈調度程序,交通燈的顏色須要在紅燈->綠燈->黃燈->紅燈之間循環轉換,可是不容許綠燈->紅燈或黃燈->綠燈等狀況。這屬於交通規則的常識,如今咱們用程序實現它,先看看咱們最傳統的思考和實現方式。github
首先,咱們會很容易想到須要定義一個交通燈顏色的枚舉:算法
public enum LightColor { Green, Red, Yellow }
而後,定義一個交通燈的類,在交通燈類中處理顏色轉換及相應的業務邏輯,代碼以下:設計模式
public class TrafficLight { private LightColor _lightColor; public TrafficLight() { _lightColor = LightColor.Red; } public void Turn() { if (_lightColor == LightColor.Red) { Console.WriteLine("紅燈停"); _lightColor = LightColor.Green; } else if (_lightColor == LightColor.Green) { Console.WriteLine("綠燈行"); _lightColor = LightColor.Yellow; } else if (_lightColor == LightColor.Yellow) { Console.WriteLine("黃燈亮了等一等"); _lightColor = LightColor.Red; } } }
最後,再不妨調用運行一下:併發
static void Main(string[] args) { TrafficLight light = new TrafficLight(); light.Turn(); light.Turn(); light.Turn(); light.Turn(); }
顯而易見,這段代碼是徹底知足需求的,而且邏輯嚴謹,調用方式也極其簡單,若是需求不變,這或許就是最好的實現方式了。可是通過前面設計原則的薰陶,咱們知道,需求不變是不可能的。所以,咱們很容易就會發現這段代碼存在的問題,充斥着if-else
的條件分支,這就意味着擴展困難。這裏例子簡單,可能並不明顯,可是真實項目中必然會有更多的條件分支和更多相似Turn()
的方法,這會致使整個項目擴展維護起來極其困難,由於,它嚴重違背了開閉原則。ide
其實,對於解決if-else
或switch-case
帶來的問題,咱們已經至關有經驗了,在簡單工廠模式中,咱們採用工廠方法模式抽象出生產具體類的工廠類解決了switch-case
的問題,在上一篇的策略模式中,咱們經過將方法抽象成策略類的方式,一樣解決了switch-case
的問題。這裏也不例外,咱們也必定須要抽象點什麼才行。可是具體抽象什麼呢?燈的顏色?Turn()
方法?仍是別的什麼?思路好像並非那麼清晰。不過呢,咱們發現其實這段代碼結構跟策略模式改造前的例子極其類似,咱們不妨用策略模式改造一下,看看可否知足需求,若是不知足,看看還缺點什麼,而後再進一步改造,由於咱們知道,策略模式至少能解決if-else
或switch-case
的問題。this
咱們看看策略模式改造後的代碼,先將Turn()
方法抽象成策略類:spa
public interface ITurnStrategy { void Turn(); } public class GreenLightTurnStrategy : ITurnStrategy { public void Turn() { Console.WriteLine("綠燈行"); } } public class RedLightTurnStrategy : ITurnStrategy { public void Turn() { Console.WriteLine("紅燈停"); } } public class YellowLightTurnStrategy : ITurnStrategy { public void Turn() { Console.WriteLine("黃燈亮了等一等"); } }
再看看改造後的TrafficLight
類:設計
public class TrafficLight { private ITurnStrategy _turnStrategy; public TrafficLight(ITurnStrategy turnStrategy) { _turnStrategy = turnStrategy; } public void Turn() { if (_turnStrategy != null) { _turnStrategy.Turn(); } } public void ChangeTurnStrategy(ITurnStrategy turnStrategy) { _turnStrategy = turnStrategy; } }
一切看起來彷佛都很完美,完美無缺。再來看看如何使用:code
static void Main(string[] args) { TrafficLight light = new TrafficLight(new RedLightTurnStrategy()); light.Turn(); light.ChangeTurnStrategy(new GreenLightTurnStrategy()); light.Turn(); light.ChangeTurnStrategy(new YellowLightTurnStrategy()); light.Turn(); light.Turn(); }
一用就發現了問題,調用變複雜了。其實,爲了能讓系統更容易擴展,調用時複雜一點也沒什麼,可是,另外一個致命的問題卻不能忽視,咱們但願燈顏色切換是由內部一套固定機制控制,而不是調用方來決定,若是用戶想換什麼顏色就換什麼顏色,交通規則豈不亂套了?顯然,策略模式是不知足需求的,咱們其實但願light.ChangeTurnStrategy()
這個動做,由系統本身內部完成。
既然不知足需求,那麼問題到底出在哪呢?回過頭來再梳理一下,咱們發現或許咱們的思路一開始就出現了誤差,交通燈能換顏色嗎?顯然是不能的,由於每一個燈的顏色是固定的,咱們所謂的換顏色,實際上換的是燈,難道要用工廠方法模式來創造不一樣顏色的燈?顯然也不合適,三個燈一開始就在那裏,只是循環切換而已,不存在建立的過程。實際上,咱們或許應該換一種思路,這裏明顯體現的是交通燈的三種狀態,每一種狀態下對應一種須要處理的行爲動做,同時,也只有狀態纔有切換的過程。
換一種思路後,咱們看問題的角度就不同了,看看改變思路後的代碼:
public abstract class TrafficLightState { public abstract void Handle(TrafficLight light); } public class GreenState : TrafficLightState { public override void Handle(TrafficLight light) { Console.WriteLine("綠燈行"); light.SetState(new YellowState()); } } public class RedState : TrafficLightState { public override void Handle(TrafficLight light) { Console.WriteLine("紅燈停"); light.SetState(new GreenState()); } } public class YellowState : TrafficLightState { public override void Handle(TrafficLight light) { Console.WriteLine("黃燈亮了等一等"); light.SetState(new RedState()); } } public class TrafficLight { private TrafficLightState _currentState; public TrafficLight() { _currentState = new RedState(); } public void Turn() { if (_currentState != null) { _currentState.Handle(this); } } public void SetState(TrafficLightState state) { _currentState = state; } }
其實,能夠發現,除了類名和方法名變了,代碼跟策略模式幾乎如出一轍(具體演化過程,文字難以表達清楚,能夠看一下我在B站或者公衆號上的視頻),但含義倒是天差地遠,這裏不是直接將方法抽象成策略對象,而是抽象不一樣的狀態,所以用了抽象類,而非接口(也能夠用接口,可是咱們一般會將方法抽象成接口,而將對象或屬性抽象成類);併爲每一個狀態提供處理該狀態下對應行爲的接口方法,而不是直接提供具體行爲的接口方法。
另外,參數也有所不一樣,TrafficLightState
中須要持有對TrafficLight
的引用,由於須要在具體的狀態類中處理TrafficLight
的狀態轉移。改造後的代碼再次完美知足需求,調用方又變得簡單了,狀態的轉移再次迴歸了主權:
static void Main(string[] args) { TrafficLight light = new TrafficLight(); light.Turn(); light.Turn(); light.Turn(); light.Turn(); }
這就狀態模式了,下面是交通燈示例最終的類圖:
從上面的例子中,咱們可能會很容易聯想到狀態機,咱們也常常聽到或看到有限狀態機或無限狀態機這樣的字眼,那麼有限狀態機跟狀態模式有什麼關係呢?咱們先看看有限狀態機的工做原理。有限狀態機的工做原理是,發生 事件(event) 後,根據 當前狀態(cur_state) ,決定執行的 動做(action) ,並設置 下一個狀態(nxt_state)。從交通燈例子能夠看到,事件(event) 就是TrafficLight
中的Turn()
方法,由客戶端觸發,觸發後,系統會判斷當前處於哪一種燈的狀態,而後執行相應的動做,完成以後再設置下一種燈狀態,和有限狀態機的工做原理完美對應上了。那麼,兩者是否等價呢?其實否則,狀態模式只是實現有限狀態機的一種手段而已,由於if-else
版本的實現,也是有限狀態機。
這裏算是一個小插曲,下面咱們迴歸到狀態模式。
狀態模式容許一個對象在其內部狀態改變時改變它的行爲,從而使對象看起來彷佛修改了它的類。
咱們將交通燈示例的類圖抽象一下,就能夠獲得以下狀態模式的類圖:
ConcreteState
對象來處理;switch-case
、if-else
帶來的難以維護的問題,這個很明顯,沒什麼好說的;這個問題須要單獨說,咱們不難發現,狀態模式雖然解決了不少問題,可是每次狀態的切換都須要建立一個新的狀態類,而本來它僅僅是一個小小的枚舉值而已,這樣一對比,對象重複的建立資源開銷是否過於巨大?其實,要解決對象重複建立的問題,咱們知道,單例模式和享元模式都是不錯的選擇,具體選用哪個,就要看狀態類的數量和我的的喜愛了。
下面是採用享元模式改進的代碼,首先是熟悉的享元工廠,代碼很簡單:
public class LightStateFactory { private static readonly IDictionary<Type, TrafficLightState> _lightStates = new Dictionary<Type, TrafficLightState>(); private static readonly object _locker = new object(); public static TrafficLightState GetLightState<TLightState>() where TLightState : TrafficLightState { Type type = typeof(TLightState); if (!_lightStates.ContainsKey(type)) { lock (_locker) { if (!_lightStates.ContainsKey(type)) { TrafficLightState typeface = Activator.CreateInstance(typeof(TLightState)) as TrafficLightState; _lightStates.Add(type, typeface); } } } return _lightStates[type]; } }
使用就更簡單了,將建立狀態對象的地方換成享元工廠建立就能夠了,代碼片斷以下:
public override void Handle(TrafficLight light) { Console.WriteLine("紅燈停"); light.SetState(LightStateFactory.GetLightState<GreenState>()); }
這裏須要特別提一下,因爲狀態是單例的,能夠在多個上下文間共享,而任什麼時候候,涉及到全局共享就不得不考慮併發的問題。所以,除非明確須要共享,不然狀態類中不該持有其它的資源,否則可能產生併發問題。一樣的緣由,狀態類也不要經過屬性或字段的方式持有對Context的引用,這也是我採用局部變量對TrafficLight
進行傳參的緣由。
其實,從類圖和實現方式上能夠看出,狀態模式和策略模式真的很像,可是因爲策略模式更具備通常性,所以更容易想到。並且,咱們也知道狀態模式和策略模式都能解決if-else
帶來的問題,關鍵就在於策略和狀態的識別,就如上述交通燈例子,剛開始識別成策略也很難發現有什麼不對。再舉一個更通俗的例子,老師會根據同窗的考試成績對同窗給出不一樣的獎懲方案,如成績低於60分的同窗罰款,成績高於90分的同窗獎錢,可是怎麼獎怎麼罰,都是老師決定的(否則全考90分以上,老師得哭)。這裏是普通的條件分支,沒有枚舉,可是咱們依然能夠看出,這裏體現的是根據不一樣的分數段採起不一樣的策略,能夠採用策略模式。再例如,一樣是考試成績,父母對你設置一個指標,考了60如下,罰錢,考了90分以上,獎錢。這時是策略仍是狀態呢?感受好像均可以,但實際上,仔細思考會發現,或許視爲狀態會更好,即在不一樣的狀態會有一個對應的動做,但狀態的有哪些呢?分數段?獎罰?狀態又是怎麼轉移的呢?還得仔細斟酌,這裏例子簡單,或許能想清楚(其實不必定),但實際項目中,估計就沒這麼容易了。
不過呢,一旦咱們識別出了狀態,而後識別出了會根據必定的觸發條件發生狀態轉移,那麼十有八九就可使用狀態模式了。
源碼連接
更多內容,歡迎關注公衆號: