概述
如果在开发过程中,出现大量的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。
可以通过反射,反射出所有的策略。这样比较符合开闭原则。