允許一個對象在其內部狀態改變時改變它的行爲。對象看起來似乎修改了它的類。
場景
我們在製作一個網上書店的網站,用戶在書店買了一定金額的書後可以升級爲銀會員、黃金會員,不同等級的會員購買書籍有不同的優惠。你可能會想到可以在User類的BuyBook方法中判斷用戶歷史消費的金額來給用戶不同的折扣,在GetUserLevel方法中根據用戶歷史消費的金額來輸出用戶的等級。帶來的問題有三點:
不用等級的用戶給予的優惠比率是經常發生變化的,一旦變化是不是就要修改User類呢?
網站在初期可能最高級別的用戶是黃金會員,而隨着用戶消費金額的累計,我們可能要增加鑽石、白金等會員類型,這些會員的折扣又是不同的,發生這樣的變化是不是又要修改User類了呢?
拋開變化不說,User類承擔了用戶等級判斷、購買折扣計算等複雜邏輯,複雜的User類代碼的可維護性會不會很好呢?
由此引入State模式,通過將對象和對象的狀態進行分離,把對象狀態的轉化以及由不同狀態產生的行爲交給具體的狀態類去做,解決上述問題。
- using System;
- using System.Collections.Generic;
- using System.Text;
- namespace StateExample
- {
- class Program
- {
- static void Main(string[] args)
- {
- User user = new User("zhuye");
- user.BuyBook(2000);
- user.BuyBook(2000);
- user.BuyBook(2000);
- user.BuyBook(2000);
- }
- }
- class User
- {
- private UserLevel userLevel;
- public UserLevel UserLevel
- {
- get { return userLevel; }
- set { userLevel = value; }
- }
- private string userName;
- private double paidMoney;
- public double PaidMoney
- {
- get { return paidMoney; }
- }
- public User(string userName)
- {
- this.userName = userName;
- this.paidMoney = 0;
- this.UserLevel = new NormalUser(this);
- }
- public void 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();
- }
- }
- abstract class UserLevel
- {
- protected User user;
- public UserLevel(User user)
- {
- this.user = user;
- }
- public abstract void StateCheck();
- public abstract double CalcRealAmount(double amount);
- }
- class DiamondUser : UserLevel
- {
- public DiamondUser(User user)
- : base(user) { }
- public override double CalcRealAmount(double amount)
- {
- return amount * 0.7;
- }
- public override void StateCheck()
- {
- }
- }
- class GoldUser : UserLevel
- {
- public GoldUser(User user)
- : base(user) { }
- public override double CalcRealAmount(double amount)
- {
- return amount * 0.8;
- }
- public override void StateCheck()
- {
- if (user.PaidMoney > 5000)
- user.UserLevel = new DiamondUser(user);
- }
- }
- class SilverUser : UserLevel
- {
- public SilverUser(User user)
- : base(user) { }
- public override double CalcRealAmount(double amount)
- {
- return amount * 0.9;
- }
- public override void StateCheck()
- {
- if (user.PaidMoney > 2000)
- user.UserLevel = new GoldUser(user);
- }
- }
- class NormalUser : UserLevel
- {
- public NormalUser(User user)
- : base(user) { }
- public override double CalcRealAmount(double amount)
- {
- return amount * 0.95;
- }
- public override void StateCheck()
- {
- if (user.PaidMoney > 1000)
- user.UserLevel = new SilverUser(user);
- }
- }
- }
User類型是環境角色。它的作用一是定義了客戶端感興趣的方法(購買書籍),二是擁有一個狀態實例,它定義了用戶的當前狀態(普通會員、銀會員、黃金會員還是鑽石會員)。
UserLevel類型是抽象狀態角色。它的作用一是定義了和狀態相關的行爲的接口,二是擁有一個環境實例,用於在一定條件下修改環境角色的抽象狀態。
NormalUser、 SilverUser、GoldUser以及DiamondUser就是具體狀態了。它們都實現了抽象狀態角色定義的接口。
爲User轉化UserLevel的操作是在各個具體狀態中進行的。在這裏可以看到這種狀態的轉化是有序列的,這樣也只會有前後兩個狀態產生依賴。假設現在的會員系統中是沒有鑽石用戶的,那麼GoldUser的StateCheck()方法中應該是沒有什麼代碼的,即使以後再要加DiamondUser類,我們也只需要修改GoldUser的SateCheck()方法,以根據一定的規則來爲環境轉化狀態。
在這裏,我們在環境中調用了StateCheck方法,在實際應用中,你可以根據需要在抽象狀態中引入模版方法,對外公開這個模版方法,並且在模版方法中調用行爲方法和轉化狀態的方法,當然,具體的行爲還是由具體狀態來實現的。
何時採用
從代碼角度來說,如果一個類有多種狀態,並且在類內部通過的條件語句判斷的類狀態來實現不同行爲時候可以把這些行爲單獨封裝爲狀態類。
從應用角度來說,如果一個對象有多種狀態,如果希望把對象狀態的轉化以及由不同狀態產生的行爲交給具體的狀態類去做,那麼可以考慮狀態模式。
實現要點
在環境角色中擁有狀態角色的實例。
在狀態角色中擁有環境角色的實例用於在具體狀態中修改環境角色的狀態。
狀態對象之間的依賴可以通過加載外部配置的轉化規則表等方法來消除。
狀態模式和策略模式的主要區別是,前者的行爲實現方式是由條件決定的,並且應當能不在客戶端干預的情況下自己遷移到合適的狀態,而後者的行爲實現方式是由客戶端選擇的,並且能隨時替換。
注意事項
過多的狀態對象可能會增加系統負擔,可以考慮把各種狀態角色實現爲無狀態對象的享元,需要保存的額外狀態由環境角色進行統一管理和處理。