一、基礎概念
????????狀態模式的本質是【根據狀態來分離和選擇行為】。
????????狀態模式的定義:允許一個對象在其內部狀態改變時改變它的行為;對象看起來似乎修改了它的類。
序號 | 認識狀態模式 | 說明 |
1 | 狀態和行為 | 通常指的是對象實例的屬性的值;而行為指的就是對象的功能(具體的說行為大多對應到方法上);狀態模式的功能【分離狀態行為,通過維護狀態的變化,來調用不同狀態對應的不同功能】(即:狀態和行為是相關聯的,它們的關系可以描述為:狀態決定行為);由于狀態是在運行期間被改變的,因此行為也會在運行期間根據狀態的改變而改變;看起來同一個對象,在不同的運行時刻,行為是不一樣的,就像是類被修改了一樣。 |
2 | 行為的平行性 | 指的是【各個狀態的行為所處的層次是一樣的,相互獨立的,沒有關聯的,是根據不同的狀態決定到底走平行線的哪一條】行為是不同的,當然對應的實現也是不同的,相互之間不可以替換。平等性強調的時可替換性,大家是同一行為的不同描述或實現;因此在同一個行為發生的時候,可以根據條件挑選任意一個實現來進行相應的處理。【大家可能會發現狀態模式的結構和策略模式的結構完全一樣,但是,它們的目的、實現、本質卻是完全不一樣的。還有行為之間的特性也是狀態模式和策略模式一個很重要的區別,?狀態模式的行為是平行性的,不可相互替換的。二策略模式的行為是平等性的,是可以相互替換的】 |
3 | 上下文和狀態處理對象 | 在狀態模式中,上下文是持有狀態的對象,但是上下文自身并不處理跟狀態相關的行為,而是把處理狀態的功能委托給了狀態對應的狀態處理類來處理。在具體的狀態處理類中經常需要獲取上下文自身的數據,甚至在必要的時候會回調上下文的方法,因此,通常將上下文自身當做一個參數傳遞給具體的狀態處理類。客戶端一般只和上下文交互。客戶端可以用狀態對象來配置一個上下文,一旦配置完畢,就不再需要和狀態對象打交道了。客戶端通常不負責運行期間狀態的維護,也不負責決定后續到底使用哪一個具體的狀態處理對象。 |
4 | 不完美的開放封閉原則 (OCP,Open Closed Principle)體驗 | 如何實現擴展? 比如我們現在拓展了正常投票狀態給正常投票的用戶給予積分獎勵;但是怎么讓VoteManager使用這個新的實現類呢?按照目前的實現,只能去修改VoteManager這個類的Vote方法。這樣一來雖然實現了我們的功能,但是并沒有完全遵守OCP原則(其實在我們實際設計開發的時候,設計原則是指導,但是并不一定要完全遵守,完全遵守設計原則幾乎是不可能完成的任務) |
5 | 創建和銷毀狀態對象 | 在使用狀態模式的時候,需要考慮究竟何時創建和銷毀狀態對象?通常有如下三種方式: ?實際上,在開發過程中,第三種方式是首選的(因為它兼顧了前兩種方式的優點且又避免了它們的缺點,幾乎可以適應各種情況需要)【只是這個方案在實現的時候,需要實現一個合理的緩存框架,而且需要考慮多線程并發的問題,因為需要由緩存框架來在合適的時候銷毀狀態對象,因此實現上難度稍大】 |
6 | 狀態的維護和轉換控制 | 狀態的維護指【維護狀態的數據,給狀態設置不同的狀態值】;狀態轉換指【根據狀態的變化來選擇不同的狀態處理對象】。在狀態模式中,通常由兩個地方可以進行狀態的維護和轉換控制: 《1》在上下文中:因為狀態本身通常被實現為上下文對象的狀態,因此可以在上下文中進行狀態維護,當然也可以控制狀態的轉換了。 《2》在狀態的處理類中:當每個狀態處理對象處理完自身狀態所對應的功能后,可以根據需要指定后繼的狀態,以便應用能正確處理后續的請求。 那么到底如何選擇這兩種方式呢? 《1》如果狀態轉換的規則是一定的,一般不需要進行什么擴展規則,適合在上下文中統一進行狀態的維護。 《2》如果狀態的轉換取決于前一個狀態動態處理的結果(或者是依賴于外部的數據)為了增加靈活性,此時在狀態處理類中進行狀態的維護。 |
序號 | 狀態模式的優點 | 狀態模式的缺點 |
1 | 簡化應用邏輯控制 ????????狀態模式使用單獨的類來封裝一個狀態的處理。如果把一個大的程序控制分為很多小塊,每塊定義一個狀態來代表,那么就可以把這些邏輯控制的代碼分散到很多單獨的類中去,這樣就把著眼點從執行狀態提高到整個對象狀態,使得代碼結構化和意圖更清晰,從而簡化應用的邏輯控制。對于依賴于狀態的if-else,理論上來講,也可以改變成應用狀態模式來實現,把每個if或else塊定義一個狀態來代表,那么就可以把塊內的功能代碼移動到狀態處理類中,從而減少if-else,避免出現巨大的條件語句 | 一個狀態對應一個狀態處理類,會使得程序引入太多的狀態類,使程序變得雜亂 |
2 | 更好的分離狀態和行為 ????????狀態模式通過設置所有狀態類的公共接口,把狀態和狀態對應的行為分離開,把所有與一個特定的狀態相關的行為都放入到一個對象中,使得程序在控制的時候,只需要關心狀態的切換,而不用關心這個狀態對應的真正處理。 | |
3 | 更好的擴展性 ????????引入了狀態處理的公共接口后,使得擴展新的狀態變得非常容易,只需要新增加一個實現狀態處理的公共接口的實現類,然后在進行狀態維護的地方,設置狀態編號到這個新的狀態即可。 | |
4 | 顯示化進行狀態轉換 ????????狀態模式引入獨立對象,使得狀態的轉換變得更加明確,而且狀態對象可以保證上下文不會發生內部狀態不一致的情況,因為上下文中只有一個變量來記錄狀態對象,只要為這一個變量賦值就可以了。 |
????????何時選用狀態模式?
????????1、如果一個對象的行為取決于它的狀態,而且必須在運行時刻根據狀態來改變它的行為,可以使用狀態模式,來把狀態和行為分離開。雖然分離開了,但狀態和行為是有對應關系的,可以在運行期間,通過改變狀態,就能夠調用到該狀態對應的狀態處理對象上去,從而改變對象的行為。
????????2、如果一個操作中含有龐大的多分支語句,而且這些分支依賴于該對象的狀態,可以使用狀態模式,把各個分支的處理分散包裝到單獨的對象處理類中,這樣,這些分支對應的對象就可以不依賴于其他對象而獨立變化了。
二、狀態模式示例
????????業務需求:現在有一個在線投票的系統,需要實現控制同一個用戶只能投一票功能;如果一個用戶反復投票,且投票的次數超過5次,則判斷為惡意刷票,要取消該用戶投票的資格,同時需要取消他所投的票數。如果一個用戶的投票次數超過了8次,將進入黑名單,禁止在登錄和使用系統。
?2.1、不使用模式的示例
分析需求,在這個投票需求中,其實分為四種情況:
1、用戶正常投票;
2、用戶正常投票后,有意或無意地重復投票;
3、用戶惡意投票;
4、黑名單用戶。
《1》投票管理類
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace StatePattern
{/// <summary>/// 投票管理/// </summary>internal class VoteManager{//記錄用戶投票的結果<用戶名稱,投票選項>private Dictionary<string,string>voteResultDic= new Dictionary<string,string>();//記錄用戶投票次數<用戶名稱,投票次數>private Dictionary<string,int>voteCountDic= new Dictionary<string,int>();//用戶投票次數int oldVoteCount = 0;/// <summary>/// 投票方法/// </summary>/// <param name="user">投票用戶</param>/// <param name="voteItem">投票選項</param>public void Vote(string user,string voteItem){//1-若投票次數容器不包含該用戶則新增否則從容器獲取后再操作if (!voteCountDic.ContainsKey(user)){if (oldVoteCount == 0){oldVoteCount += 1;voteCountDic.Add(user, oldVoteCount);}}else{oldVoteCount= voteCountDic[user];if (oldVoteCount>=1){oldVoteCount += 1;voteCountDic[user] = oldVoteCount;}}//判斷用戶投票的類型(判斷是正常投票、重復投票還是惡意投票)if (oldVoteCount == 1){//正常投票,則記錄到投票記錄voteResultDic.Add(user, voteItem);Console.WriteLine($"恭喜你【{user}】投給【{voteItem}】【{voteCountDic[user]}】票成功");}else if (oldVoteCount > 1 && oldVoteCount < 5){//重復投票,暫不處理Console.WriteLine($"請不要重復投票,【{user}】已經投給【{voteItem}】票【{oldVoteCount}】次");} else if (oldVoteCount>=5 && oldVoteCount<8){//惡意投票,取消用戶投票資格,并取消投票記錄string voteResult = voteResultDic[user];if (!string.IsNullOrEmpty(voteResult)){voteResultDic[user]="";}Console.WriteLine($"你有惡意刷票行為,取消投票資格并清空投票記錄;【{user}】已投給【{voteItem}】票【{voteCountDic[user]}】次");}else if (oldVoteCount>=8){//記入黑名單,禁止登錄系統Console.WriteLine($"進入黑名單,禁止登錄和使用本系統;【{user}】已投給【{voteItem}】票【{voteCountDic[user]}】次");}}}//Class_end
}
《2》客戶端測試
using StatePattern.StateDemoThree;namespace StatePattern
{internal class Program{static void Main(string[] args){VoteManagerTest();Console.ReadLine();}/// <summary>/// 投票管理測試/// </summary>private static void VoteManagerTest(){Console.WriteLine("---投票管理測試---");VoteManager voteManager = new VoteManager();for (int i = 0; i < 10; i++){voteManager.Vote("張三","A");}}}//Class_end
}
《3》運行結果
????????現在我們的實現是達到業務要求了;但是在Vote()方法中有很多的判斷,且每個判斷對應的功能處理都放在一起,有些雜亂。現在的問題是:
《1》如果現在需要修改某種投票情況所對應的具體功能處理,那就需要在Vote()方法中尋找到相應的代碼塊,然后進行改動。
《2》如果要添加新的功能(如投票超過8次但不足10次,給個機會,只是禁止登錄和使用系統3天;如果再犯才永久封號,這樣該怎么處理?【這就需要修改投票管理代碼,在if-else結構中再添加另外一個else-if的塊進行處理】)。
????????這兩種情況,不管那種都需要在原有的代碼中修改;那么該如何實現才能夠做到【即能夠容易地給Vote()方法添加新的功能,又能夠很方便地修改已有的功能呢?】
?2.2、使用狀態模式的示例1——上下文統一管理狀態及其具體狀態行為的調用
使用狀態模式解決上面問題的思路是:
????????我們發現其實用戶投票分為了四種投票狀態,且各個狀態和對應的功能具有很強的對應性(即:每個狀態下的各自處理是不同的,不存在相互替換的可能);
????????那么為了解決上面提出的問題,一個設計就是:把狀態和狀態對應的行為從原來的雜亂代碼中分離出來,把每個狀態所對應的功能都封裝在一個單獨的類里面,這樣選擇不同處理的時候,其實就是在選擇不同的狀態處理類。為了統一操作這些不同的狀態類,則定義一個狀態接口來約束它們,這樣外部就可以面向這個統一狀態接口編程,而無須關心具體的狀態類實現了。這樣一來,要修改某種投票情況所對應的具體功能處理,只需要直接修改或擴展某個狀態處理類就可以了。
? 2.2.1、定義投票接口——規范對外提供的行為
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace StatePattern.StateDemoOne
{/// <summary>/// 投票狀態接口/// </summary>internal interface IVoteState{//處理狀態對象的行為void Vote(string user,string voteItem,VoteManager voteManager);}//Interface_end
}
? 2.2.2、具體各種投票狀態對應處理類的實現
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace StatePattern.StateDemoOne
{/// <summary>/// 正常投票/// </summary>internal class NormalVoteState : IVoteState{public void Vote(string user, string voteItem, VoteManager voteManager){//正常投票,則記錄到投票記錄if (!voteManager.GetVoteResultDic().ContainsKey(user)){voteManager.GetVoteResultDic().Add(user,voteItem);}Console.WriteLine($"恭喜你【{user}】投給【{voteItem}】票成功");}}//Class_end
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace StatePattern.StateDemoOne
{/// <summary>/// 重復投票/// </summary>internal class RepeatVoteState : IVoteState{public void Vote(string user, string voteItem, VoteManager voteManager){//重復投票,不做處理Console.WriteLine($"請不要重復投票,【{user}】已經投給【{voteItem}】票");}}//Class_end
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace StatePattern.StateDemoOne
{internal class SpiteVoteState : IVoteState{public void Vote(string user, string voteItem, VoteManager voteManager){//惡意投票,取消用戶投票資格,并取消投票記錄if (voteManager.GetVoteResultDic().ContainsKey(user)){voteManager.GetVoteResultDic()[user] = "";}Console.WriteLine($"你有惡意刷票行為,取消投票資格并清空投票記錄;【{user}】已投給【{voteItem}】票");}}//Class_end
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace StatePattern.StateDemoOne
{internal class BlackVoteState : IVoteState{public void Vote(string user, string voteItem, VoteManager voteManager){//記入黑名單,禁止登錄系統Console.WriteLine($"進入黑名單,禁止登錄和使用本系統;【{user}】已投給【{voteItem}】票");}}//Class_end
}
? 2.2.3、投票管理實現——相當于狀態模式的上下文
投票管理是實現對各種狀態的判斷與調用管理。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace StatePattern.StateDemoOne
{/// <summary>/// 投票管理/// </summary>internal class VoteManager{//持有狀態處理對象private IVoteState voteState = null;//記錄用戶投票的結果<用戶名稱,投票選項>private Dictionary<string, string> voteResultDic = new Dictionary<string, string>();//記錄用戶投票次數<用戶名稱,投票次數>private Dictionary<string, int> voteCountDic = new Dictionary<string, int>();/// <summary>/// 獲取記錄用戶投票的結果容器/// </summary>/// <returns></returns>public Dictionary<string, string> GetVoteResultDic(){return voteResultDic;}//投票public void Vote(string user,string voteItem){int oldVoteCount = 0;if (!voteCountDic.ContainsKey(user)){oldVoteCount += 1;voteCountDic.Add(user,oldVoteCount);}else{oldVoteCount= voteCountDic[user];oldVoteCount += 1;voteCountDic[user]= oldVoteCount;}if (oldVoteCount == 1){voteState = new NormalVoteState();}else if (oldVoteCount > 1 && oldVoteCount < 5){voteState = new RepeatVoteState();}else if (oldVoteCount >= 5 && oldVoteCount < 8){voteState = new SpiteVoteState();}else if (oldVoteCount >= 8){voteState = new BlackVoteState();}//然后調用狀態對象進行相應的操作voteState.Vote(user,voteItem,this);}}//Class_end
}
? 2.2.4、客戶端測試
using StatePattern.StateDemoThree;namespace StatePattern
{internal class Program{static void Main(string[] args){VoteManagerByStatePattern();Console.ReadLine();}/// <summary>/// 使用狀態模式進行投票管理測試/// </summary>private static void VoteManagerByStatePattern(){Console.WriteLine("---使用狀態模式進行投票管理測試---");StateDemoOne.VoteManager voteManager = new StateDemoOne.VoteManager();for (int i = 0; i < 10; i++){voteManager.Vote("張三", "A");}}}//Class_end
}
? 2.2.5、運行結果
在這個狀態模式實現的示例中可以看出:【狀態的轉換是在內部實現的,主要在狀態模式內部(VoteManager)里面維護】(如:對于投票的人員,任何時候他的操作都是投票,但是投票管理對象的處理卻不一定一樣,會根據投票的次來判斷狀態,然后根據狀態去選擇不同的處理)。?
? 2.2.6、不完美的OCP擴展
?比如我們現在需要對正常投票狀態對應的功能進行修改(即:
1、對正常的投票用戶給予積分獎勵,那么只需要擴展正常投票的狀態對應類;但是VoteManager類里面的維護還是需要修改原有代碼將原有的NormalVoteState()修改NormalVoteStateExtand();
2、需要給投票超過8次但是不足10次的,給個機會,只是禁止登錄和使用系統3天,如果再犯才進入黑名單【要實現這個功能,需要對原有的投票超過8次的狀態判定VoteManager類里面進行修改(實現超過8次但不足10次)是黑名單警告,最后才是黑名單;然后在實現一個黑名單警告的具體處理邏輯】)。
《1》擴展正常投票類
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace StatePattern.StateDemoOne
{internal class NormalVoteStateExtand:NormalVoteState,IVoteState{public new void Vote(string user, string voteItem, VoteManager voteManager){//先調用已有的功能base.Vote(user, voteItem, voteManager);//然后在給與積分獎勵Console.WriteLine("獎勵積分10分");}}//Class_end
}
《2》實現黑名單警告類
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace StatePattern.StateDemoOne
{internal class BlackWarnVoteState : IVoteState{public void Vote(string user, string voteItem, VoteManager voteManager){//待進入黑名單警告狀態Console.WriteLine("禁止登錄和使用系統3天");}}//Class_end
}
《3》投票管理類投票狀態及其具體類使用的修改
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace StatePattern.StateDemoOne
{/// <summary>/// 投票管理/// </summary>internal class VoteManager{//持有狀態處理對象private IVoteState voteState = null;//記錄用戶投票的結果<用戶名稱,投票選項>private Dictionary<string, string> voteResultDic = new Dictionary<string, string>();//記錄用戶投票次數<用戶名稱,投票次數>private Dictionary<string, int> voteCountDic = new Dictionary<string, int>();/// <summary>/// 獲取記錄用戶投票的結果容器/// </summary>/// <returns></returns>public Dictionary<string, string> GetVoteResultDic(){return voteResultDic;}//投票public void Vote(string user,string voteItem){int oldVoteCount = 0;if (!voteCountDic.ContainsKey(user)){oldVoteCount += 1;voteCountDic.Add(user,oldVoteCount);}else{oldVoteCount= voteCountDic[user];oldVoteCount += 1;voteCountDic[user]= oldVoteCount;}if (oldVoteCount == 1){//voteState = new NormalVoteState();voteState = new NormalVoteStateExtand();}else if (oldVoteCount > 1 && oldVoteCount < 5){voteState = new RepeatVoteState();}else if (oldVoteCount >= 5 && oldVoteCount < 8){voteState = new SpiteVoteState();}//else if (oldVoteCount >= 8)//{// voteState = new BlackVoteState();//}else if (oldVoteCount >= 8 && oldVoteCount < 10){voteState = new BlackWarnVoteState();}else if (oldVoteCount >= 10){voteState = new BlackVoteState();}//然后調用狀態對象進行相應的操作voteState.Vote(user,voteItem,this);}}//Class_end
}
《4》客戶端不用修改
using StatePattern.StateDemoThree;namespace StatePattern
{internal class Program{static void Main(string[] args){VoteManagerByStatePattern();Console.ReadLine();}/// <summary>/// 使用狀態模式進行投票管理測試/// </summary>private static void VoteManagerByStatePattern(){Console.WriteLine("---使用狀態模式進行投票管理測試---");StateDemoOne.VoteManager voteManager = new StateDemoOne.VoteManager();for (int i = 0; i < 10; i++){voteManager.Vote("張三", "A");}}}//Class_end
}
《5》運行結果
2.3、使用狀態模式的示例2——在每個具體的狀態行為下調用下一個狀態行為
? 2.3.1、定義投票接口——規范對外提供的行為
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace StatePattern.StateDemoTwo
{/// <summary>/// 投票狀態接口(規范投票狀態對外的相關行為)/// </summary>internal interface IVoteState{//處理狀態對象的行為void Vote(string user,string voteItem,VoteManager voteManager);}//Interface_end
}
? 2.3.2、具體各種投票狀態對應處理類的實現
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace StatePattern.StateDemoTwo
{internal class NormalVoteState : IVoteState{public void Vote(string user, string voteItem, VoteManager voteManager){//正常投票,則記錄到投票記錄if (!voteManager.GetVoteResultDic().ContainsKey(user)){voteManager.GetVoteResultDic().Add(user, voteItem);}Console.WriteLine($"恭喜你【{user}】投給【{voteItem}】票成功");//正常投票完成,維護下一個狀態,同一個人在投票就重復了if (!voteManager.GetVoteStateDic().ContainsKey(user)){voteManager.GetVoteStateDic().Add(user, new RepeatVoteState());}else{voteManager.GetVoteStateDic()[user] = new RepeatVoteState();}}}//Class_end
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace StatePattern.StateDemoTwo
{internal class RepeatVoteState : IVoteState{public void Vote(string user, string voteItem, VoteManager voteManager){//重復投票,不做處理Console.WriteLine($"請不要重復投票,【{user}】已經投給【{voteItem}】票");//重復投票完成,維護下一個狀態,重復投票到5次,就算惡意投票了;注意這里是判斷大于等于4,因為在這里設置的是下一個狀態if (voteManager.GetVoteCountDic()[user]>=4){if (!voteManager.GetVoteStateDic().ContainsKey(user)){voteManager.GetVoteStateDic().Add(user, new SpiteVoteState());}else{voteManager.GetVoteStateDic()[user]=new SpiteVoteState();}}}}//Class_end
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace StatePattern.StateDemoTwo
{internal class SpiteVoteState : IVoteState{public void Vote(string user, string voteItem, VoteManager voteManager){//惡意投票,取消用戶投票資格,并取消投票記錄if (voteManager.GetVoteResultDic().ContainsKey(user)){voteManager.GetVoteResultDic()[user] = "";}Console.WriteLine($"你有惡意刷票行為,取消投票資格并清空投票記錄;【{user}】已投給【{voteItem}】票");//惡意投票完成,維護下一個狀態,投票到8次則進入黑名單,這里的判斷是大于等于7if (voteManager.GetVoteCountDic()[user]>=7){if (!voteManager.GetVoteStateDic().ContainsKey(user)){voteManager.GetVoteStateDic().Add(user, new BlackVoteState());}else{voteManager.GetVoteStateDic()[user] = new BlackVoteState();}}}}//Class_end
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace StatePattern.StateDemoTwo
{internal class BlackVoteState : IVoteState{public void Vote(string user, string voteItem, VoteManager voteManager){//記入黑名單,禁止登錄系統Console.WriteLine($"進入黑名單,禁止登錄和使用本系統;【{user}】已投給【{voteItem}】票");}}//Class_end
}
? 2.2.3、投票管理實現——相當于狀態模式的上下文
?這里需要在投票管理類中新增一個投票狀態容器,用來記錄每個用戶對應的投票狀態。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace StatePattern.StateDemoTwo
{/// <summary>/// 投票管理/// </summary>internal class VoteManager{//持有狀態處理對象private IVoteState voteState = null;//記錄當前每個用戶對應的狀態處理對象,每個用戶當前的狀態是不同的<用戶名稱,當前對應的狀態處理對象>private Dictionary<string,IVoteState> voteStateDic = new Dictionary<string,IVoteState>();//記錄用戶投票的結果<用戶名稱,投票的選項>private Dictionary<string ,string> voteResultDic= new Dictionary<string ,string>();//記錄用戶投票的次數private Dictionary<string,int>voteCountDic= new Dictionary<string ,int>();//獲取用戶狀態的容器public Dictionary<string, IVoteState> GetVoteStateDic(){return voteStateDic;}//獲取用戶投票的結果容器public Dictionary<string, string> GetVoteResultDic(){return voteResultDic;}//獲取用戶投票的次數容器public Dictionary<string, int> GetVoteCountDic(){return voteCountDic;}//投票public void Vote(string user,string voteItem){int oldVoteCount = 0;//1-先為該用戶增加投票次數if (!voteCountDic.ContainsKey(user)){oldVoteCount += 1;voteCountDic.Add(user, oldVoteCount);}else{oldVoteCount = voteCountDic[user];oldVoteCount += 1;voteCountDic[user] = oldVoteCount;}//2-獲取該用戶的投票狀態voteState = new NormalVoteState();//如果沒有投票狀態,說明還沒有投過票,那就初始化一個正常的投票狀態if (!voteStateDic.ContainsKey(user)){voteStateDic.Add(user, voteState);}else{voteState=voteStateDic[user];}//調用投票方法進行操作voteState.Vote(user,voteItem,this);}}//Class_end
}
? 2.2.4、客戶端測試
using StatePattern.StateDemoThree;namespace StatePattern
{internal class Program{static void Main(string[] args){VoteManagerTwoByStatePattern();Console.ReadLine();}/// <summary>/// 使用狀態模式進行投票管理測試2/// </summary>private static void VoteManagerTwoByStatePattern(){Console.WriteLine("---使用狀態模式進行投票管理測試2---");StateDemoTwo.VoteManager voteManager = new StateDemoTwo.VoteManager();for (int i = 0; i < 10; i++){voteManager.Vote("張三", "A");}}}//Class_end
}
? 2.2.5、運行結果
? 2.2.6、不完美的OCP擴展
????????新增需求:現在需要實現投票超過8次但不足10次的,給個機會,只是禁止登錄和使用系統3天,如果再犯,在拉入黑名單。
《1》新增警告狀態類
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace StatePattern.StateDemoTwo
{internal class BlackWarnVoteState : IVoteState{public void Vote(string user, string voteItem, VoteManager voteManager){//待進入黑名單警告狀態Console.WriteLine("禁止登錄和使用系統3天");//待進入黑名單警告處理完成,維護下一個狀態,投票到10次,就進入黑名單,這里的判斷是大于等于9if (voteManager.GetVoteCountDic()[user]>=9){if (!voteManager.GetVoteStateDic().ContainsKey(user)){voteManager.GetVoteStateDic().Add(user, new BlackVoteState());}else{voteManager.GetVoteStateDic()[user] = new BlackVoteState();}}}}//Class_end
}
?《2》修改使用到原來拉入黑名單的惡意刷票類里面修改下一步操作為警告狀態
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace StatePattern.StateDemoTwo
{internal class SpiteVoteState : IVoteState{public void Vote(string user, string voteItem, VoteManager voteManager){//惡意投票,取消用戶投票資格,并取消投票記錄if (voteManager.GetVoteResultDic().ContainsKey(user)){voteManager.GetVoteResultDic()[user] = "";}Console.WriteLine($"你有惡意刷票行為,取消投票資格并清空投票記錄;【{user}】已投給【{voteItem}】票");//惡意投票完成,維護下一個狀態,投票到8次則進入黑名單,這里的判斷是大于等于7if (voteManager.GetVoteCountDic()[user]>=7){if (!voteManager.GetVoteStateDic().ContainsKey(user)){//voteManager.GetVoteStateDic().Add(user, new BlackVoteState());voteManager.GetVoteStateDic().Add(user, new BlackWarnVoteState());}else{//voteManager.GetVoteStateDic()[user] = new BlackVoteState();voteManager.GetVoteStateDic()[user] = new BlackWarnVoteState();}}}}//Class_end
}
《3》客戶端測試——不變
using StatePattern.StateDemoThree;namespace StatePattern
{internal class Program{static void Main(string[] args){VoteManagerTwoByStatePattern();Console.ReadLine();}/// <summary>/// 使用狀態模式進行投票管理測試2/// </summary>private static void VoteManagerTwoByStatePattern(){Console.WriteLine("---使用狀態模式進行投票管理測試2---");StateDemoTwo.VoteManager voteManager = new StateDemoTwo.VoteManager();for (int i = 0; i < 10; i++){voteManager.Vote("張三", "A");}}}//Class_end
}
《4》運行結果
?2.4、可使用數據庫來維護狀態
????????在實際開發中,還可以使用數據庫來維護狀態(即:在數據庫中存儲一個狀態的識別數據【將維護下一個狀態演化成了維護下一個狀態的識別數據(如狀態編碼)】)。
如果使用數據庫來維護狀態,實現思路如下:
????????《1》在每個具體的狀態處理類中,原本在處理完成后,需要判斷下一個狀態是什么,然后在創建下一個狀態對象,并設置會上下文中。【如果使用數據庫的方式,就不用創建下一個狀態對象,也不用設置會上下文中,而是把下一個狀態對應的編碼記入到數據庫中就可以了】。
????????《2》在上下文(如:VoteManager這個投票管理類中,則不用記錄所有用戶狀態的容器,而是直接從數據庫中獲取該用戶當前對應的狀態編碼,然后根據狀態編碼創建出對應的狀態對象即可);
????????《3》如果向數據庫中存儲下一個狀態對象的編碼,那么上下文中就不再需要持有狀態對象了,相當于把這個功能放到數據庫中了【但是需要注意,數據庫存儲的只是狀態編碼,而不是狀態對象,獲取到數據庫中的狀態編碼后,在程序中仍然需要根據狀態編碼去創建對應的狀態對象】(如果想要程序更加通用一些,可以通過配置文件來配置狀態編碼和對應的狀態處理類【也可以直接在數據庫中記錄狀態編碼和對應的狀態處理類,這樣在上下文中,先獲取下一個狀態的狀態編碼,然后根據狀態編碼去獲取對應的類,然后通過反射來創建具體的狀態對象,這樣就避免了一長串的if-else;且以后再添加新的狀態編碼和狀態處理對象也不用修改代碼了】)
?2.5、模擬工作流——請假流程
????????業務需求:有一個請假流程,流程內容為當某個人提出請假申請,先由項目經理審批,如果項目經理同意審批直接結束,再查看請假的天數是否超過3天,項目經理的審批權限最多只有3天,如果請假天數在3天內,那么審批也直接結束;否則就提交給部門經理;部門經理審核通過后,無論是否同意,審批都直接結束。業務流程圖如下:
?思路分析:
????????把請假單在流程中的各個階段狀態分析出來(即:該請假單的狀態有:等待項目經理審核、等待部門經理審核、審核結束)詳細的狀態驅動流程如下:
《1》請假人填寫請假單,提交請假單,此時請假單的狀態是等待項目經理審核狀態;
《2》當項目經理審核完成后,若不同意,則請假單的狀態是審核結束狀態;如果同意且請假天數在3天內,請假單的狀態是審核結束狀態;如果同意且請假天數大于3天,則請假單的狀態是等待部門經理審核狀態;
《3》當部門經理審核完成后,無論是否同意,請假單的狀態都是審核結束了。
????????既然可以把流程看作是狀態驅動的,那么自然可以自然地使用狀態模式,每次當相應的工作人員完成工作,請求流程響應的時候,流程處理的對象會根據當前所處的狀態,把流程處理委托給相應狀態對象去處理。
? 2.5.1、定義狀態處理機——作為公共的上下文
????????這里定義的狀態處理機相當于上下文,提供基礎的、公共功能:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace StatePattern.StateDemoThree
{/// <summary>/// 公共狀態處理(相當于狀態模式的Context上下文)/// 包含所有流程使用狀態模式時的公共功能/// </summary>internal class StateMachine{/// <summary>/// 持有的狀態對象/// </summary>public IState? State { get; set; }/// <summary>/// 創建流處理所需的業務數據模型(這里不知道具體類型,可使用泛型或object)/// </summary>public object? BusinessModel { get; set; }/// <summary>/// 執行工作,在客戶完成自己的業務工作后調用/// </summary>public void Dowork(){this.State.Dowork(this);}}//Class_end
}
? 2.5.2、定義狀態的公共接口——規范對外提供的功能
????????這個接口定義的處理流程功能所涉及到的業務數據就統一從上下文中傳遞而來:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace StatePattern.StateDemoThree
{/// <summary>/// 公共狀態接口/// </summary>internal interface IState{//執行狀態對象的功能處理void Dowork(StateMachine stateMachine);}//Interface_end
}
? 2.5.3、定義請假單數據模型
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace StatePattern.StateDemoThree
{/// <summary>/// 請假單/// </summary>internal class LeaveRequestModel{/// <summary>/// 請假人/// </summary>public string? User { get; set; }/// <summary>/// 請假開始時間/// </summary>public string? BeginDate { get; set; }/// <summary>/// 請假天數/// </summary>public int? LeaveDays { get;set; }/// <summary>/// 審核結果/// </summary>public string? AuditResult { get; set; }}//Class_end
}
? 2.5.4、定義處理客戶端請求的上下文
????????雖然我們這里的實現沒有擴展公共的上下文功能,但還是新定義了請假的上下文,表示可以添加自己的處理數據內容:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace StatePattern.StateDemoThree
{/// <summary>/// 客戶端請求的上下文【雖然這里并不需要擴展狀態機,但還是繼承一下狀態機,表示可以添加自己的處理】/// </summary>/// <typeparam name="T"></typeparam>internal class LeaveRequestContext:StateMachine{//在上下文這里可以擴展與自己流程相關的處理}//Class_end
}
? 2.5.5、定義處理請假流程的狀態接口
????????雖然我們這里也不需要擴展公共狀態的接口功能,但還是繼承狀態接口,表示可以自己擴展請假狀態功能:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace StatePattern.StateDemoThree
{/// <summary>/// 請假狀態接口【雖然這里不需要擴展狀態功能,但還是繼承一下狀態,表示可以添加自己的處理】/// </summary>/// <typeparam name="T"></typeparam>internal interface ILeaveRequestState: IState{//這里可以擴展與自己流程相關的處理}//Interface_end
}
? 2.5.6、實現各個具體的狀態對象
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace StatePattern.StateDemoThree
{/// <summary>/// 項目經理審核(項目經理審核后可能對應部門經理審核;或者是審核結束后的一種)/// </summary>internal class ProjectManagerState : ILeaveRequestState{public void Dowork(StateMachine stateMachine){//1-創建業務對象模型LeaveRequestModel model = (LeaveRequestModel)stateMachine.BusinessModel;//2-業務處理,把審核結果保存到數據庫中//3-分解選擇的結果和條件設置下一步驟if ("同意".Equals(model?.AuditResult)){if (model.LeaveDays > 3){//如果請假天數大于3天,且項目經理同意則提交部門經理stateMachine.State = new DepartmentManagerState();}else{//如果請假天數在3天及其以內,就由項目經理做主審批后轉為結束狀態stateMachine.State = new AuditOverState();}}else{//項目經理不同意的話,也就不用提交給部門經理了,直接轉為審核結束狀態stateMachine.State = new AuditOverState();}//4-給申請人增加一個提示,讓他可以查看當前的最新審核結果}}//Class_end
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace StatePattern.StateDemoThree
{/// <summary>/// 處理部門經理的審核(處理后對應審核結束狀態)/// </summary>internal class DepartmentManagerState : ILeaveRequestState{public void Dowork(StateMachine stateMachine){//1-先把業務模型創建出來LeaveRequestModel model = (LeaveRequestModel)stateMachine.BusinessModel;//2-業務處理,將審核結果保存到數據庫中//3-部門經理審核通過后,直接轉向審核結束狀態stateMachine.State = new AuditOverState();//4-給申請人增加一個提示,讓他可以查看當前的最新審核結果}}//Class_end
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace StatePattern.StateDemoThree
{/// <summary>/// 處理審核結束狀態/// </summary>internal class AuditOverState : ILeaveRequestState{public void Dowork(StateMachine stateMachine){//1-先把業務模型創建出來LeaveRequestModel model = (LeaveRequestModel)stateMachine.BusinessModel;//2-業務處理,在數據中記錄整個流程結束}}//Class_end
}
? 2.5.7、改進各個具體的狀態對象可以運行
????????由于前面的各個具體狀態對象沒有數據庫實現部分,且沒有對應的UI界面。因此我們這里就改造為在命令行界面模擬用戶輸入數據。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace StatePattern.StateDemoThree
{/// <summary>/// 項目經理審核(項目經理審核后可能對應部門經理審核;或者是審核結束后的一種)/// </summary>internal class ProjectManagerState2 : ILeaveRequestState{public void Dowork(StateMachine stateMachine){//1-創建業務對象模型LeaveRequestModel model = (LeaveRequestModel)stateMachine.BusinessModel;//模擬用戶處理界面,通過控制臺讀取和顯示數據Console.WriteLine("項目經理審核中,請稍后。。。");Console.WriteLine($"【{model?.User}】申請從【{model?.BeginDate}】開始請假【{model?.LeaveDays}】天,請審核(1:同意;2:不同意)");string? strInput=Console.ReadLine();if (!string.IsNullOrEmpty(strInput)){//設置會上下文中string result = "不同意";if ("1".Equals(strInput)){result = "同意";}model.AuditResult = result;}//2-業務處理,把審核結果保存到數據庫中//3-分解選擇的結果和條件設置下一步驟if ("同意".Equals(model?.AuditResult)){if (model.LeaveDays > 3){//如果請假天數大于3天,且項目經理同意則提交部門經理stateMachine.State = new DepartmentManagerState2();//為部門經理增加工作stateMachine.Dowork();}else{//如果請假天數在3天及其以內,就由項目經理做主審批后轉為結束狀態stateMachine.State = new AuditOverState2();stateMachine.Dowork();}}else{//項目經理不同意的話,也就不用提交給部門經理了,直接轉為審核結束狀態stateMachine.State = new AuditOverState2();stateMachine.Dowork();}//4-給申請人增加一個提示,讓他可以查看當前的最新審核結果}}//Class_end
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace StatePattern.StateDemoThree
{/// <summary>/// 處理部門經理的審核(處理后對應審核結束狀態)/// </summary>internal class DepartmentManagerState2 : ILeaveRequestState{public void Dowork(StateMachine stateMachine){//1-先把業務模型創建出來LeaveRequestModel model = (LeaveRequestModel)stateMachine.BusinessModel;//模擬用戶處理界面Console.WriteLine("部門經理審核中,請稍后。。。");Console.WriteLine($"【{model?.User}】申請從【{model?.BeginDate}】開始請假【{model?.LeaveDays}】天,請審核(1:同意;2:不同意)");string? strInput = Console.ReadLine();if (!string.IsNullOrEmpty(strInput)){//設置會上下文中string result = "不同意";if ("1".Equals(strInput)){result = "同意";}model.AuditResult = result;}//2-業務處理,將審核結果保存到數據庫中//3-部門經理審核通過后,直接轉向審核結束狀態stateMachine.State = new AuditOverState2();stateMachine.Dowork();//4-給申請人增加一個提示,讓他可以查看當前的最新審核結果}}//Class_end
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace StatePattern.StateDemoThree
{/// <summary>/// 處理審核結束狀態/// </summary>internal class AuditOverState2 : ILeaveRequestState{public void Dowork(StateMachine stateMachine){//1-先把業務模型創建出來LeaveRequestModel model = (LeaveRequestModel)stateMachine.BusinessModel;//2-業務處理,在數據中記錄整個流程結束Console.WriteLine($"【{model.User}】你的請假申請流程審核結束,最終審核結果是【{model.AuditResult}】");}}//Class_end
}
? ?2.5.8、客戶端測試
using StatePattern.StateDemoThree;namespace StatePattern
{internal class Program{static void Main(string[] args){LeaveRequestTest();Console.ReadLine();}/// <summary>/// 請假流程審批/// </summary>private static void LeaveRequestTest(){//創建業務對象并設置業務數據StateDemoThree.LeaveRequestModel requestModel = new StateDemoThree.LeaveRequestModel();requestModel.User = "張三";requestModel.BeginDate = DateTime.Now.ToString("F");requestModel.LeaveDays = 5;//創建上下文對象StateDemoThree.LeaveRequestContext requestContext= new StateDemoThree.LeaveRequestContext();//為上下文設置業務對象模型requestContext.BusinessModel = requestModel;//配置上下文狀態requestContext.State = new ProjectManagerState2();//開始運行requestContext.Dowork();}}//Class_end
}
? ?2.5.9、運行結果
三、項目源碼工程
kafeiweimei/Learning_DesignPattern: 這是一個關于C#語言編寫的基礎設計模式項目工程,方便學習理解常見的26種設計模式https://github.com/kafeiweimei/Learning_DesignPattern?tab=readme-ov-file