目录
1.背景
之前在看《重构 改善既有代码的设计》一书,在看到Replace Type Code With State/Strategy(用状态模式/策略模式替换类型码)一节时遇到一个困惑:怎么用策略模式替换switch case代码?所幸的时,两天前查资料的时候偶然看到 圣殿骑士 的博客,他写的《31天重构学习》系列博客让我受益匪浅,也让我领悟到了怎么使用策略模式替换swith case代码,并在此基础之上编写了一个Demo,以供分享、交流。
2.案例
功能:简易计算器
项目结构如图1
图1
其中:StrategyPatternDemo为核心计算类库
Calculator.Client为计算器客户端,是命令行程序
3.swich…case…方式实现
StrategyPatternDemo类库里包括了IOperation(计算操作,如+-*/)和ICalculator(计算)两个接口以及Operation和Calculator两个实现类。
具体实现代码如下:
/// <summary> /// 计算操作接口 /// </summary> public interface IOperation { #region 属性 /// <summary> /// 操作名称 /// </summary> string Name { get; } /// <summary> /// 操作符号 /// </summary> string Symbol { get; } /// <summary> /// 操作数量 /// </summary> int NumberOperands { get; } #endregion }
IOperation
/// <summary> /// 计算 /// </summary> public interface ICalculator { /// <summary> /// 计算 /// </summary> /// <param name="operation">具体的操作</param> /// <param name="operands">操作数</param> /// <returns></returns> double Operation(IOperation operation, double[] operands); }
ICalculator
public class Operation:IOperation { #region IOperation interface implementation public string Name { get; private set; } public string Symbol { get; private set; } public int NumberOperands { get; private set; } #endregion #region Constructors public Operation(string name,string sysmbol,int numberOperands) { this.Name = name; this.Symbol = sysmbol; this.NumberOperands = numberOperands; } #endregion }
Operation
public sealed class Calculator : ICalculator { #region ICalculator interface implementation public double Operation(IOperation operation, double[] operands) { if (operation==null) { return ; } switch (operation.Symbol) { case "+": return operands[] + operands[]; case "-": return operands[] - operands[]; case "*": return operands[] * operands[]; case "/": return operands[] / operands[]; default: throw new InvalidOperationException(string.Format("invalid operation {0}",operation.Name)); } } #endregion }
Calculator
客户端程序:
代码如下:
class Program { public ICalculator calculator = new StrategyPatternDemo.Calculator(); private IList<IOperation> operationList=new List<IOperation> { new Operation("加","+",), new Operation("减","-",), new Operation("乘","*",), new Operation("除","/",), }; public IEnumerable<IOperation> Operations { get { return operationList; } } public void Run() { var operations = this.Operations; var operationsDic = new SortedList<string, IOperation>(); foreach (var item in operations) { Console.WriteLine("操作名称:{0},操作符号:{1},操作个数:{2}", item.Name,item.Symbol, item.NumberOperands); operationsDic.Add(item.Symbol, item); } Console.WriteLine("--------------------------------------------------------------------"); string selectedOp = string.Empty; do { try { Console.Write("输入计算操作符号: "); selectedOp = Console.ReadLine(); if (selectedOp.ToLower() == "exit" || !operationsDic.ContainsKey(selectedOp)) { continue; } var operation = operationsDic[selectedOp]; double[] operands = new double[operation.NumberOperands]; for (int i = ; i < operation.NumberOperands; i++) { Console.Write("\t 第{0}个操作数:", i + ); string selectedOperand = Console.ReadLine(); operands[i] = double.Parse(selectedOperand); } Console.WriteLine("使用计算器"); double result = calculator.Operation(operation, operands); Console.WriteLine("计算结果:{0}", result); Console.WriteLine("--------------------------------------------------------------------"); } catch (FormatException ex) { Console.WriteLine(ex.Message); Console.WriteLine(); continue; } } while (selectedOp != "exit"); } static void Main(string[] args) { var p = new Program(); p.Run(); } }
Program
运行结果如图2:
图2
整体思路是对区分计算操作符号进行switch操作,根据不同的符号进行计算。
4.switch…case…带来的问题
上一小节就带来一个问题,如果我要添加求余计算(计算符号为%),那么必须要修改Calculator类才行,这样就违反了面向对象的开放封闭设计原则。
怎么做呢?怎样才能实现不修改Calculator类就达到扩展的目的呢?
5.使用策略模式重构switch…case…代码
一个解决方案就是使用策略模式重构代码
5.1策略模式的概念
策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。
UML图:
5.2重构方案:
扩展IOperation接口,提供一个计算方法
#region 算法 double Calculator(double[] operands); #endregion
/// <summary> /// 计算操作接口 /// </summary> public interface IOperation { #region 属性 /// <summary> /// 操作名称 /// </summary> string Name { get; } /// <summary> /// 操作符号 /// </summary> string Symbol { get; } /// <summary> /// 操作数量 /// </summary> int NumberOperands { get; } #endregion #region 算法 double Calculator(double[] operands); #endregion }
IOperation
修改Operation类(相当于UML中的Strategy),实现Calculator方法,修改后的代码如下:
public class Operation:IOperation { #region IOperation interface implementation public string Name { get; private set; } public string Symbol { get; private set; } public int NumberOperands { get; private set; } public virtual double Calculator(double[] operands) { throw new NotImplementedException(); } protected void CheckOperands(double[] operands) { if (operands == null) { throw new ArgumentNullException("operands"); } if (operands.Length != this.NumberOperands) { throw new ArgumentException("operands is not equal to NumberOperands"); } } #endregion #region Constructors public Operation(string name,string sysmbol,int numberOperands) { this.Name = name; this.Symbol = sysmbol; this.NumberOperands = numberOperands; } #endregion
Operation
添加加减乘除的具体实现类,分别为:AddOperation、SubOperation、MulOperation、DivOperation
代码如下:
/// <summary> /// 加法操作 /// </summary> public class AddOperation:Operation { public AddOperation() : base("加","+",) { } public override double Calculator(double[] operands) { base.CheckOperands(operands); return operands[] + operands[]; } }
AddOperation
/// <summary> /// 减法操作 /// </summary> public class SubOperation:Operation { public SubOperation() : base("减","-",) { } public override double Calculator(double[] operands) { base.CheckOperands(operands); return operands[] - operands[]; } }
SubOperation
/// <summary> /// 乘法操作 /// </summary> public class MulOperation:Operation { public MulOperation() : base("乘", "*", ) { } public override double Calculator(double[] operands) { base.CheckOperands(operands); return operands[] * operands[]; } }
MulOperation
/// <summary> /// 除法操作 /// </summary> public class DivOperation:Operation { public DivOperation() : base("除", "/", ) { } public override double Calculator(double[] operands) { base.CheckOperands(operands); if (operands[]==) { throw new ArgumentException("除数不能为0"); } return operands[] / operands[]; } }
DivOperation
修改ICalculator接口(相当于UML中的Context),修改后的代码如下:
/// <summary> /// 计算 /// </summary> public interface ICalculator { /// <summary> /// 计算 /// </summary> /// <param name="operation">具体的操作</param> /// <param name="operands">操作数</param> /// <returns></returns> double Operation(IOperation operation, double[] operands); /// <summary> /// 策略模式重构需要添加的 /// </summary> /// <param name="operation">计算符号</param> /// <param name="operands">操作数</param> /// <returns></returns> double OperationWithNoSwitch(string operation, double[] operands); /// <summary> /// 判断操作符号是否存在 /// </summary> /// <param name="operationSymbol"></param> /// <returns></returns> bool KeyIsExist(string operationSymbol); /// <summary> /// 根据操作符号获取操作数 /// </summary> /// <param name="operationSymbol"></param> /// <returns></returns> int OperationNumberOperands(string operationSymbol); }
ICalculator
修改Calculator类,实现新增的方法,修改后的代码如下:
public sealed class Calculator : ICalculator { #region Constructors public Calculator() { } public Calculator(IEnumerable<IOperation> operations) { this.operationDic = operations.ToDictionary(u=>u.Symbol); } #endregion #region ICalculator interface implementation public double Operation(IOperation operation, double[] operands) { if (operation==null) { return ; } switch (operation.Symbol) { case "+": return operands[] + operands[]; case "-": return operands[] - operands[]; case "*": return operands[] * operands[]; case "/": return operands[] / operands[]; default: throw new InvalidOperationException(string.Format("invalid operation {0}",operation.Name)); } } #endregion #region 策略模式重构需要添加的内容 private readonly IDictionary<string,IOperation> operationDic; public double OperationWithNoSwitch(string operation, double[] operands) { if (!KeyIsExist(operation)) { throw new ArgumentException(" operationSysmbol is not exits "); } return this.operationDic[operation].Calculator(operands); } public bool KeyIsExist(string operationSymbol) { return this.operationDic.ContainsKey(operationSymbol); } public int OperationNumberOperands(string operationSymbol) { if (!KeyIsExist(operationSymbol)) { throw new ArgumentException(" operationSysmbol is not exits "); } return this.operationDic[operationSymbol].NumberOperands; } #endregion }
Calculator
修改客户端类:
添加 ReconsitutionRun() 方法表示运行重构后的代码
求改后的代码为:
class Program { public ICalculator calculator = new StrategyPatternDemo.Calculator(); private IList<IOperation> operationList=new List<IOperation> { new Operation("加","+",), new Operation("减","-",), new Operation("乘","*",), new Operation("除","/",), }; public IEnumerable<IOperation> Operations { get { return operationList; } } public void Run() { var operations = this.Operations; var operationsDic = new SortedList<string, IOperation>(); foreach (var item in operations) { Console.WriteLine("操作名称:{0},操作符号:{1},操作个数:{2}", item.Name,item.Symbol, item.NumberOperands); operationsDic.Add(item.Symbol, item); } Console.WriteLine("--------------------------------------------------------------------"); string selectedOp = string.Empty; do { try { Console.Write("输入计算操作符号: "); selectedOp = Console.ReadLine(); if (selectedOp.ToLower() == "exit" || !operationsDic.ContainsKey(selectedOp)) { continue; } var operation = operationsDic[selectedOp]; double[] operands = new double[operation.NumberOperands]; for (int i = ; i < operation.NumberOperands; i++) { Console.Write("\t 第{0}个操作数:", i + ); string selectedOperand = Console.ReadLine(); operands[i] = double.Parse(selectedOperand); } Console.WriteLine("使用计算器"); double result = calculator.Operation(operation, operands); Console.WriteLine("计算结果:{0}", result); Console.WriteLine("--------------------------------------------------------------------"); } catch (FormatException ex) { Console.WriteLine(ex.Message); Console.WriteLine(); continue; } } while (selectedOp != "exit"); } /// <summary> /// 重构后的代码 /// </summary> IEnumerable<IOperation> operationList2 = new List<IOperation> { new AddOperation(), new SubOperation(), new MulOperation(), new DivOperation(), }; public ICalculator calculator2; public void ReconsitutionRun() { calculator2 = new StrategyPatternDemo.Calculator(operationList2); Console.WriteLine("------------------------重构后的执行结果-----------------------------"); foreach (var item in operationList2) { Console.WriteLine("操作名称:{0},操作符号:{1},操作个数:{2}", item.Name, item.Symbol, item.NumberOperands); } Console.WriteLine("--------------------------------------------------------------------"); string selectedOp = string.Empty; do { try { Console.Write("输入计算操作符号: "); selectedOp = Console.ReadLine(); if (selectedOp.ToLower() == "exit" || !this.calculator2.KeyIsExist(selectedOp)) { continue; } var operandsCount = this.calculator2.OperationNumberOperands(selectedOp); double[] operands = new double[operandsCount]; for (int i = ; i < operandsCount; i++) { Console.Write("\t 第{0}个操作数:", i + ); string selectedOperand = Console.ReadLine(); operands[i] = double.Parse(selectedOperand); } Console.WriteLine("使用计算器"); double result = calculator2.OperationWithNoSwitch(selectedOp, operands); Console.WriteLine("计算结果:{0}", result); Console.WriteLine("--------------------------------------------------------------------"); } catch (FormatException ex) { Console.WriteLine(ex.Message); Console.WriteLine(); continue; } } while (selectedOp != "exit"); } static void Main(string[] args) { var p = new Program(); //p.Run(); p.ReconsitutionRun(); } }
Program
重构后的代码执行结果图3:
图3
经过重构后的代码正确运行。
5.3扩展
下面回到上文提到的用switch代码带来的问题一:扩展求余运算。
在Calculator.Client客户端项目中添加一个新类:ModOperation,代码如下:
/// <summary> /// 求余 /// </summary> public class ModOperation:Operation { public ModOperation() : base("余", "%", ) { } public override double Calculator(double[] operands) { base.CheckOperands(operands); return operands[] % operands[]; } }
ModOperation
修改客户端类,将求余运算类添加到上下文中(Calculator)
/// <summary> /// 重构后的代码 /// </summary> IEnumerable<IOperation> operationList2 = new List<IOperation> { new AddOperation(), new SubOperation(), new MulOperation(), new DivOperation(), new ModOperation(), };
运行结果图4:
图4
经过重构后的代码,可以不修改Calculator类达到扩展算法的目的。
6.总结
通过对实现一个简单计算器的功能来说明了如何使用策略模式重构swith...case...代码,经过重构后的代码可以轻松实现扩展新算法而无需修改原有代码,符合了面向对象的开闭设计原则:对修改关闭,对扩展开放。
在此感谢 圣殿骑士 给我带来的灵感和使用重构的方法,让我对策略模式和重构的认识更进一步。