二十三種設計模式[21] - 策略模式(Strategy Pattern)
前言
策略模式,對象行為型模式的一種。在《設計模式 - 可復用的面向對象軟件》一書中將之描述為“ 定義一些列的算法,把它們一個個封裝起來,並且使它們可相互替換。使得算法可以獨立於使用它的客戶而變化 ”。
也就是說通過策略模式,我們能夠將算法與其調用者分離成相對獨立的個體,降低維護成本,使代碼更加優雅。
場景
就拿數據的搜索來說,可以簡單的分為模糊搜索和精確搜索。在開發這個功能時,可能會寫出如下代碼。
public List<string> Search(string keyword, SeachModeEnum mode) { //模擬數據 List<string> dataList = new List<string> { "abc", "Abc", "ABc", "ABC" }; switch (mode) { case SeachModeEnum.Exact: return dataList.FindAll(t => t == keyword); case SeachModeEnum.Fuzzy: return dataList.FindAll(t => t.IndexOf(keyword) > -1); default: return new List<string>(); } } public enum SeachModeEnum { /// <summary> /// 精確 /// </summary> Exact, /// <summary> /// 模糊 /// </summary> Fuzzy }
上面的代碼確實能夠解決我們的需求,但這只是一個簡單的業務邏輯,如果換成一個復雜的業務算法呢?那麽Search函數會變得非常龐大、非常復雜。並且在同一時刻switch中只有一個算法復合當前運行的需求,也就意味著在運行時刻switch中存在著許多與當前業務無關的“垃圾代碼”。後續,當我們需要增加新的算法時需要對swtich語句做出修改,隨著業務的日漸龐大維護的成本也隨之加大。
下面,來看看如何用策略模式實現類似的業務。
結構
- Strategy(策略接口):用來定義所有算法的公共接口,保證所有算法實現類的一致性;
- ConcreteStrategy(具體策略):策略接口的實現類,用來實現具體的算法;
- Context(上下文):定義用戶感興趣的功能。保持Strategy的引用,並將用戶的請求轉發給Strategy去處理;
在策略模式中,Context依賴於Strategy,Context本身並不實現具體的業務而是將用戶的請求轉發給具體的Strategy實現。
示例
還是以數據搜索為例,我們首先為每一種搜索方式創建一個實現IStrategy接口的類,以及一個調用各個檢索方式的上下文類SearchContext。如下。
public interface IStrategy { SearchContext Context { set; get; } List<string> Search(string keyword); } /// <summary> /// 精確搜索 /// </summary> public class ExactSearchStrategy : IStrategy { public SearchContext Context { set; get; } = null; public List<string> Search(string keyword) { return this.Context.DataList.FindAll(t => t == keyword); } } /// <summary> /// 模糊搜索 /// </summary> public class FuzzySearchStrategy : IStrategy { public SearchContext Context { set; get; } = null; public List<string> Search(string keyword) { return this.Context.DataList.FindAll(t => t.IndexOf(keyword) > -1); } } public class SearchContext { public SearchContext(IStrategy strategy) { this.Strategy = strategy; this.Strategy.Context = this; this.DataList = new List<string> { "abc","Abc","ABc","ABC" }; } private IStrategy Strategy { set; get; } = null; /// <summary> /// 模擬數據 /// </summary> public List<string> DataList { private set; get; } = new List<string>(); public List<string> Search(string keyword) { return this.Strategy.Search(keyword); } } static void Main(string[] args) { Console.WriteLine("精確搜索:"); SearchContext context = new SearchContext(new ExactSearchStrategy()); var dataList = context.Search("AB"); dataList.ForEach(t => Console.WriteLine(t)); Console.WriteLine("-------------------------"); Console.WriteLine("模糊搜索:"); context = new SearchContext(new FuzzySearchStrategy()); dataList = context.Search("AB"); dataList.ForEach(t => Console.WriteLine(t)); Console.ReadKey(); }
示例中,分別定義了實現IStrategy接口的ExactSearchStrategy和FuzzySearchStrategy策略類來實現模糊搜索和精確搜索業務。同時在這兩個策略類中保持了一個SearchContext的引用以便在執行搜索時獲取策略類感興趣的信息(比如示例中的DataList)。當然也可以將這些信息通過參數傳遞給策略類(比如示例中的keyword)。但並不是所有的策略類都對這些信息感興趣。保持SearchContext的引用能夠使Context避免創建一些策略不感興趣的數據,但同時也會造成Context與Strategy的循環引用,使其更緊密的耦合。用戶在使用SearchContext時將其感興趣的搜索策略註入到上下文中,再由SearchContext將用戶的請求轉發給註入的搜索策略類即可實現不同的搜索方式。
在此基礎上,我們在增加精確且不區分大小寫和模糊且不區分大小寫兩種搜索方式。如下。
/// <summary> /// 精確且不區分大小寫 /// </summary> public class ExactAndCaseInsensitiveSearchStrategy : IStrategy { public SearchContext Context { set; get; } = null; public List<string> Search(string keyword) { return this.Context.DataList.FindAll(t => t.ToLower() == keyword.ToLower()); } } /// <summary> /// 模糊且不區分大小寫 /// </summary> public class FuzzyAndCaseInsensitiveSearchStrategy : IStrategy { public SearchContext Context { set; get; } = null; public List<string> Search(string keyword) { return this.Context.DataList.FindAll(t => t.ToLower().IndexOf(keyword.ToLower()) > -1); } } static void Main(string[] args) { Console.WriteLine("精確搜索:"); //SearchContext context = new SearchContext(new ExactSearchStrategy()); SearchContext context = new SearchContext(new ExactAndCaseInsensitiveSearchStrategy()); var dataList = context.Search("AB"); dataList.ForEach(t => Console.WriteLine(t)); Console.WriteLine("-------------------------"); Console.WriteLine("模糊搜索:"); //context = new SearchContext(new FuzzySearchStrategy()); context = new SearchContext(new FuzzyAndCaseInsensitiveSearchStrategy()); dataList = context.Search("AB"); dataList.ForEach(t => Console.WriteLine(t)); Console.ReadKey(); }
如示例中所示,我們在使用策略模式的過程中可以很輕易的增加或修改使用的算法。只需要增加相應的策略類並修改Context中註入的策略即可增加和修改算法。符合開閉原則。
補充
策略模式 + 單例/享元模式
在策略模式中,當我們只通過參數傳遞策略感興趣的數據時這個策略類只存在內部行為而不存在外部狀態。所以可以將策略類作為享元共享或者將其設計為一個單例。
- 享元 (Flyweight)
- 單件 (Singleton)
策略模式與狀態模式
策略模式與狀態模式的實現很相似。並且都是能夠改變類的行為以及減少判斷語句的使用。但策略模式的核心在於算法的封裝,使用戶可以很容易且低成本的變更算法,並且一般情況是這個過程是靜態的。而狀態模式的核心在於動態的變更對象的行為,在程序的運行過程中由於一些條件的觸發,導致上下文(Conatext)中的狀態(State)對象發生變更從而改變了上下文的行為。
總結
策略模式可以幫助我們減少條件語句if-else和switch-case的使用。將每種算法單獨封裝起來即降低了用戶對算法的使用、維護以及切換的成本,也易於代碼的理解和擴展,復合開閉原則。但策略模式的使用也使得類的數量增多,並且在某些情況下用戶需要知道這些策略到底有何不同,此時就不得不向客戶暴露策略類具體的實現。
以上,就是我對策略模式的理解,希望對你有所幫助。
示例源碼:https://gitee.com/wxingChen/DesignPatternsPractice
系列匯總:https://www.cnblogs.com/wxingchen/p/10031592.html
本文著作權歸本人所有,如需轉載請標明本文鏈接(https://www.cnblogs.com/wxingchen/p/10264077.html)
二十三種設計模式[21] - 策略模式(Strategy Pattern)