意圖
允許一個對象在其內部狀態改變時改變它的行為。對象看起來似乎修改了它的類。
情境
我們在製作一個網上書店的網站,使用者在書店買了一定金額的書後可以升級為銀會員、黃金會員,不同等級的會員購買書籍有不同的優惠。你可能會想到可以在User類的BuyBook方法中判斷使用者曆史消費的金額來給使用者不同的折扣,在GetUserLevel方法中根據使用者曆史消費的金額來輸出使用者的等級。帶來的問題有三點:
l 不用等級的使用者給予的優惠比率是經常發生變化的,一旦變化是不是就要修改User類呢?
l 網站在初期可能最進階別的使用者是黃金會員,而隨著使用者消費金額的累計,我們可能要增加鑽石、白金等會員類型,這些會員的折扣又是不同的,發生這樣的變化是不是又要修改User類了呢?
l 拋開變化不說,User類承擔了使用者等級判斷、購買折扣計算等複雜邏輯,複雜的User類代碼的可維護性會不會很好呢?
由此引入State模式,通過將對象和對象的狀態進行分離,把對象狀態的轉化以及由不同狀態產生的行為交給具體的狀態類去做,解決上述問題。
範例程式碼
using System;using System.Collections.Generic;using System.Text;namespace StateExample{ classProgram { staticvoid Main(string[] args) { User user = newUser("zhuye"); user.BuyBook(2000); user.BuyBook(2000); user.BuyBook(2000); user.BuyBook(2000); } } classUser { privateUserLevel userLevel; publicUserLevel UserLevel { get { return userLevel; } set { userLevel = value; } } privatestring userName; privatedouble paidMoney; publicdouble PaidMoney { get { return paidMoney; } } public User(string userName) { this.userName = userName; this.paidMoney = 0; this.UserLevel = newNormalUser(this); } publicvoid BuyBook(double amount) { Console.WriteLine(string.Format("Hello {0}, You have paid ${1}, You Level is {2}.", userName, paidMoney, userLevel.GetType().Name)); double realamount = userLevel.CalcRealAmount(amount); Console.WriteLine("You only paid $" + realamount + " for this book."); paidMoney += realamount; userLevel.StateCheck(); } } abstractclassUserLevel { protectedUser user; public UserLevel(User user) { this.user = user; } publicabstractvoid StateCheck(); publicabstractdouble CalcRealAmount(double amount); } classDiamondUser : UserLevel { public DiamondUser(User user) : base(user) { } publicoverridedouble CalcRealAmount(double amount) { return amount * 0.7; } publicoverridevoid StateCheck() { } } classGoldUser : UserLevel { public GoldUser(User user) : base(user) { } publicoverridedouble CalcRealAmount(double amount) { return amount * 0.8; } publicoverridevoid StateCheck() { if (user.PaidMoney > 5000) user.UserLevel = newDiamondUser(user); } } classSilverUser : UserLevel { public SilverUser(User user) : base(user) { } publicoverridedouble CalcRealAmount(double amount) { return amount * 0.9; } publicoverridevoid StateCheck() { if (user.PaidMoney > 2000) user.UserLevel = newGoldUser(user); } } classNormalUser : UserLevel { public NormalUser(User user) : base(user) { } publicoverridedouble CalcRealAmount(double amount) { return amount * 0.95; } publicoverridevoid StateCheck() { if (user.PaidMoney > 1000) user.UserLevel = newSilverUser(user); } }}
代碼執行結果如:
代碼說明
l User類型是環境角色。它的作用一是定義了用戶端感興趣的方法(購買書籍),二是擁有一個狀態執行個體,它定義了使用者的目前狀態(普通會員、銀會員、黃金會員還是鑽石會員)。
l UserLevel類型是抽象狀態角色。它的作用一是定義了和狀態相關的行為的介面,二是擁有一個環境執行個體,用於在一定條件下修改環境角色的抽象狀態。
l NormalUser、 SilverUser、GoldUser以及DiamondUser就是具體狀態了。它們都實現了抽象狀態角色定義的介面。
l 為User轉化UserLevel的操作是在各個具體狀態中進行的。在這裡可以看到這種狀態的轉化是有序列的,這樣也只會有前後兩個狀態產生依賴。假設現在的會員系統中是沒有鑽石使用者的,那麼GoldUser的StateCheck()方法中應該是沒有什麼代碼的,即使以後再要加DiamondUser類,我們也只需要修改GoldUser的SateCheck()方法,以根據一定的規則來為環境轉化狀態。
l 在這裡,我們在環境中調用了StateCheck方法,在實際應用中,你可以根據需要在抽象狀態中引入模版方法,對外公開這個模版方法,並且在模版方法中調用行為方法和轉化狀態的方法,當然,具體的行為還是由具體狀態來實現的。
何時採用
l 從代碼角度來說,如果一個類有多種狀態,並且在類內部通過的條件陳述式判斷的類狀態來實現不同行為時候可以把這些行為單獨封裝為狀態類。
l 從應用角度來說,如果一個對象有多種狀態,如果希望把對象狀態的轉化以及由不同狀態產生的行為交給具體的狀態類去做,那麼可以考慮狀態模式。
實現要點
l 在環境角色中擁有狀態角色的執行個體。
l 在狀態角色中擁有環境角色的執行個體用於在具體狀態中修改環境角色的狀態。
l 狀態物件之間的依賴可以通過載入外部配置的轉化規則表等方法來消除。
l 狀態模式和策略模式的主要區別是,前者的行為實現方式是由條件決定的,並且應當能不在用戶端幹預的情況下自己遷移到合適的狀態,而後者的行為實現方式是由用戶端選擇的,並且能隨時替換。
注意事項
l 過多的狀態物件可能會增加系統負擔,可以考慮把各種狀態角色實現為無狀態對象的享元,需要儲存的額外狀態由環境角色進行統一管理和處理。