概述
如果在開發過程中,出現大量的if else或者switch case 語句,如果這些語句塊中的代碼並不是包含業務邏輯,只是單純的分流方法,那么,每一個語句塊中都是一個算法或者叫策略。
背景
比如在最近項目中遇到的問題。一個二維碼字符串解析的方法:
微信的二維碼掃描結果包含“WeChat”,解析規則是拿着文本到微信服務器解析,返回解析對象。
支付寶二維碼掃描結果包含“Alipay”,解析規則是使用“->”分割字符串得到解析對象。
最簡單快速的代碼就是直接if else判斷:
1 /// <summary> 2 /// 解析方法 3 /// </summary> 4 /// <param name="text">掃描得到的文本</param> 5 public void AnalysisAction(string text) 6 { 7 //微信解析方法 8 if (text.Contains("WeChat")) 9 { 10 //拿着text到微信服務器解析,返回解析對象。 11 } 12 //支付寶解析方法 13 else if (text.Contains("Alipay")) 14 { 15 //使用->分割,得到解析對象。 16 } 17 }
問題
當然使用這種方式是可以的,但是如果以后又加入一種掃碼解析方法:
中國聯通二維碼掃描文本中包含“Unicom”,解析規則為以“:”分割,得到解析對象。
那么你就要繼續添加else if(text.Contains("Unicom"))。每次增加一個新的掃描解析規則,你都要去增加else if判斷,這種是面向過程的體驗,屬於硬編碼。這也違反了面向對象的開閉原則。
改進(抽象)
我們可以使用策略模式來改進代碼。定義一系列的算法,把每一個算法封裝起來, 並且使它們可相互替換。本模式使得算法可獨立於使用它的客戶而變化。
抽象出一個掃描解析接口,定義一個解析方法。然后分別定義微信和支付寶的解析方法集成接口。
策略模式的uml圖如下:
/// <summary> /// 掃描解析規則抽象接口 /// </summary> public interface IStrategy { /// <summary> /// 掃描解析方法策略 /// </summary> /// <param name="text"></param> void AnalysisAction(string text); }
/// <summary> /// 微信掃描解析規則 /// </summary> public class StrategyWeChat : IStrategy { /// <summary> /// 掃描策略 /// </summary> /// <param name="text"></param> public void AnalysisAction(string text) { //拿着text到微信服務器解析,返回解析對象。 } }
/// <summary> /// 支付寶掃描解析規則 /// </summary> public class StrategyAlipay : IStrategy { /// <summary> /// 掃描策略 /// </summary> /// <param name="text"></param> public void AnalysisAction(string text) { //使用->分割,得到解析對象。 } }
/// <summary> /// 接口管理類 /// </summary> public class StrategyContext { private IStrategy strategy; /// <summary> /// 外層調用的時候決定使用哪個掃描策略 /// </summary> /// <param name="strategy"></param> public StrategyContext(IStrategy strategy) { this.strategy = strategy; } public void AnalysisAction(string text) { strategy.AnalysisAction(text); } }
這樣我們的業務邏輯就可以這樣寫:
private StrategyContext Context; /// <summary> /// 解析方法 /// </summary> /// <param name="text">掃描得到的文本</param> public void AnalysisAction(string text) { //微信解析方法 if (text.Contains("WeChat")) { Context = new StrategyContext(new StrategyWeChat()); } //支付寶解析方法 else if (text.Contains("Alipay")) { Context = new StrategyContext(new StrategyAlipay()); } Context.AnalysisAction(text); }
我們將具體的解析規則放到了具體的實現類中,但是我們並沒有消滅If else,如果在添加聯通的掃碼解析的話,還是需要修改代碼,添加else。
這就是策略模式的缺點,必須知道要使用的具體的策略,也就是有的人說的還是要使用if else。
升級改造
因為前面說到了策略模式的缺點。如果就是要消滅if else呢?我們可以將決定使用策略的決定權放到具體策略實現類中。
1 /// <summary> 2 /// 掃描解析規則抽象接口 3 /// </summary> 4 public interface IStrategy 5 { 6 /// <summary> 7 /// 是否可以解析 8 /// </summary> 9 bool Analysisable { get; } 10 11 /// <summary> 12 /// 掃描解析方法策略 13 /// </summary> 14 /// <param name="text"></param> 15 void AnalysisAction(); 16 }
/// <summary> /// 微信掃描解析規則 /// </summary> public class StrategyWeChat : IStrategy { private string _text; public StrategyWeChat(string text) { this._text = text; } public bool Analysisable { get { return _text.Contains("WeChat"); } } /// <summary> /// 掃描策略 /// </summary> /// <param name="text"></param> public void AnalysisAction() { //拿着_text到微信服務器解析,返回解析對象。 } }
/// <summary> /// 支付寶掃描解析規則 /// </summary> public class StrategyAlipay : IStrategy { private string _text; public StrategyAlipay(string text) { this._text = text; } public bool Analysisable { get { return _text.Contains("Alipay"); } } /// <summary> /// 掃描策略 /// </summary> /// <param name="text"></param> public void AnalysisAction() { //使用->分割,得到解析對象。 } }
1 public class StrategyContext2 2 { 3 private readonly IList<IStrategy> strategyList = new List<IStrategy>(); 4 /// <summary> 5 /// 將所有策略都方法 6 /// </summary> 7 /// <param name="text"></param> 8 public StrategyContext2(string text) 9 { 10 strategyList.Add(new StrategyWeChat(text)); 11 strategyList.Add(new StrategyAlipay(text)); 12 } 13 /// <summary> 14 /// 調用具體的策略類實現掃碼解析方法 15 /// </summary> 16 public void AnalysisAction() 17 { 18 foreach (var item in strategyList) 19 { 20 if (item.Analysisable)//判斷當前策略類是否可以處理 21 item.AnalysisAction(); 22 } 23 } 24 }
1 private StrategyContext2 Context; 2 public void AnalysisAction(string text) 3 { 4 Context = new StrategyContext2(text); 5 Context.AnalysisAction();//自動實現解析,不用關心使用哪種策略 6 }
這樣我們就想決定權放到了具體策略類本身中。消滅了If else。如果再添加聯通掃碼策略的時候,只需要添加聯通的具體掃描策略,然后在context構造函數中把他加入到策略集合中。
但是這樣我們還是修改了context代碼。如果繼續想不修改context代碼呢?
繼續升級
我們可以使用反射,將所有策略實現類都反射出來,添加到策略集合中。那么我們的context類可以這樣寫:
public class StrategyContext3 { private readonly IList<IStrategy> strategyList = new List<IStrategy>(); /// <summary> /// 將所有策略都方法 /// </summary> /// <param name="text"></param> public StrategyContext3(string text) { //查詢程序集 Assembly assembly = Assembly.GetExecutingAssembly(); //找出繼承掃描策略接口的類 IEnumerable<Type> types = assembly.GetTypes().Where(c => c.GetInterface("IStrategy") != null); foreach (var t in types) { object[] parameters = new object[1]; parameters[0] = text; //創建類的實例 strategyList.Add((IStrategy)Activator.CreateInstance(t, parameters)); } } /// <summary> /// 調用具體的策略類實現掃碼解析方法 /// </summary> public void AnalysisAction() { foreach (var item in strategyList) { if (item.Analysisable)//判斷當前策略類是否可以處理 item.AnalysisAction(); } } }
應用場景
總結
優點:策略模式是對算法的封裝,它把算法的責任和算法本身分割開,委派給不同的對象管理。
缺點:客戶端必須知道所有的策略類,並自行決定使用哪一個策略類。這就意味着客戶端必須理解這些算法的區別,以便適時選擇恰當的算法類。換言之,策略模式只適用於客戶端知道所有的算法或行為的情況。 這也就是我們所說的if else並沒有真正的被消滅。
改進:我們可以將決定使用哪種策略的權利放到策略類本身中,讓策略自己決定到底是不是用我自己的方法。從而實現消滅if else。
可以通過反射,反射出所有的策略。這樣比較符合開閉原則。
