引言
我們在編程的時候,有時候會遇到,一個對象的行為動作會由對象的狀態來決定的,也就是對象的行為是由狀態來決定,如果對象的狀態很多,那么也會由很多不同的行為,這時候我們一班會 if –else if—來判斷對象的行為,當對象的行為或者狀態發生變化時,就需要更改之前的代碼,這樣的設計就違背了開閉原則,而狀態模式就是用來解決這樣的問題的
狀態模式的介紹
- 狀態模式的定義
當一個對象的內在狀態改變時允許改變其行為,這個對象看起來像改變了其類
狀態模式主要解決的是當控制一個對象狀態轉換的條件表達式過於復雜的情況,把狀態的判斷邏輯轉移到表示不同狀態的一系列類中,可以把負責的判斷邏輯簡單化,如果這個狀態判斷很簡單,就沒畢業使用“狀態模式”了。
- 狀態模式的結構圖
- 狀態模式的組成
(1)、環境角色(Context):也稱上下文,定義客戶端所感興趣的接口,並且保留一個具體狀態類的實例。這個具體狀態類的實例給出此環境對象的現有狀態。
(2)、抽象狀態角色(State):定義一個接口,用以封裝環境對象的一個特定的狀態所對應的行為。
(3)、具體狀態角色(ConcreteState):每一個具體狀態類都實現了環境(Context)的一個狀態所對應的行為。
- 狀態模式的代碼實現
比如:支付寶中螞蟻會員等級來說明,分為:大眾會員、黃金會員、鉑金會員、鑽石會員四種, 不同的等級享受的服務不同,我們就拿免費體現的額度來比較一下,鑽石會員體現的額度是100萬,其他都是2萬的額度,感覺這差別太大了,當額度使用完以后,可以使用螞蟻積分兌換,兌換的規則是 大眾會員 1積分兌換1元體現額度,黃金會員1積分兌換1.5的額度,鉑金1積分兌換3元,鑽石1積分兌換5元
第一版
class Program { static void Main(string[] args) { Member m = new Member(); m.Membership = "大眾會員"; m.Lines = true; m.Withdrawal(); m.Membership = "黃金會員"; m.Lines = false; m.Withdrawal(); m.Membership = "鉑金會員"; m.Lines = true; m.Withdrawal(); m.Membership = "鑽石會員"; m.Lines = true; m.Withdrawal(); } } public class Member { public string Membership { get; set; } //會員等級 public bool Lines { get; set; } //免費體現額度 public void Withdrawal() { if (Membership=="大眾會員") { if (Lines) { Console.WriteLine("大眾會員1積分可以兌換1元提現額度"); } else { Console.WriteLine("您還有免費的提現額度"); } } else if (Membership=="黃金會員") { if (Lines) { Console.WriteLine("黃金1積分可以兌換1.5元提現額度"); } else { Console.WriteLine("您還有免費的提現額度"); } } else if (Membership=="鉑金會員") { if (Lines) { Console.WriteLine("鉑金會員1積分可以兌換3元提現額度"); } else { Console.WriteLine("您還有免費的提現額度"); } } else { if (Lines) { Console.WriteLine("鑽石1積分可以兌換5元提現額度"); } else { Console.WriteLine("您有100的免費提現額度"); } } } }
現在功能實現了,但是看仔細看看Member類中的Withdrawal方法,里面有很大分子判斷,這就說明它的責任過大了,無論是任何狀態,都需要通過它來改變,明顯違背了“單一職責原則”、“開發封閉原則”。
第二版
class Program { static void Main(string[] args) { Context m = new Context(); m.Action(); } } public abstract class State { public abstract void Handle(Context context); } public class PublicMember:State { public override void Handle(Context context) { if (context.Lines) { Console.WriteLine("大眾會員1積分可以兌換1元提現額度"); } else { Console.WriteLine("您還有免費的提現額度"); } context.SetState(new GoldMember()); context.Lines = true; context.Action(); } } public class GoldMember:State { public override void Handle(Context context) { if (context.Lines) { Console.WriteLine("黃金1積分可以兌換1.5元提現額度"); } else { Console.WriteLine("您還有免費的提現額度"); } context.SetState(new PlatinumMember()); context.Lines = true; context.Action(); } } public class PlatinumMember : State { public override void Handle(Context context) { if (context.Lines) { Console.WriteLine("鉑金會員1積分可以兌換3元提現額度"); } else { Console.WriteLine("您還有免費的提現額度"); } context.SetState(new DiamondMember()); context.Lines = true; context.Action(); } } public class DiamondMember : State { public override void Handle(Context context) { if (context.Lines) { Console.WriteLine("鑽石1積分可以兌換5元提現額度"); } else { Console.WriteLine("您有100的免費提現額度"); } } } public class Context { public Context() { state = new PublicMember(); Lines = true; } private State state; public string Membership { get; set; } //會員等級 public bool Lines { get; set; } //免費體現額度 public void SetState(State s) { state = s; } public void Action() { state.Handle(this); } }
狀態模式的優缺點
- 狀態模式的優點
(1)、封裝了轉換規則。
(2)、枚舉可能的狀態,在枚舉狀態之前需要確定狀態種類。
(3)、將所有與某個狀態有關的行為放到一個類中,並且可以方便地增加新的狀態,只需要改變對象狀態即可改變對象的行為。
(4)、允許狀態轉換邏輯與狀態對象合成一體,而不是某一個巨大的條件語句塊。
(5)、可以讓多個環境對象共享一個狀態對象,從而減少系統中對象的個數。 - 狀態模式的缺點
(1)、狀態模式的使用必然會增加系統類和對象的個數。
(2)、狀態模式的結構與實現都較為復雜,如果使用不當將導致程序結構和代碼的混亂。
(3)、狀態模式對“開閉原則”的支持並不太好,對於可以切換狀態的狀態模式,增加新的狀態類需要修改那些負責狀態轉換的源代碼,否則無法切換到新增狀態;而且修改某個狀態類的行為也需修改對應類的源代碼。
總結
在對象的行動取決於本身的狀態時,可以適用於狀態模式,免去了過多的if–else判斷,這對於一些復雜的和繁瑣的判斷邏輯有很好的幫助。但是使用狀態模式,勢必會造成更多的接口和類,對於非常簡單的狀態判斷,可以不使用

