使用策略模式重构switch case 代码
目录
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两个实现类。
具体实现代码如下:
1 /// <summary> 2 3 /// 计算操作接口 4 5 /// </summary> 6 7 public interface IOperation 8 9 { 10 11 #region 属性 12 13 /// <summary> 14 15 /// 操作名称 16 17 /// </summary> 18 19 string Name { get; } 20 21 /// <summary> 22 23 /// 操作符号 24 25 /// </summary> 26 27 string Symbol { get; } 28 29 /// <summary> 30 31 /// 操作数量 32 33 /// </summary> 34 35 int NumberOperands { get; } 36 37 #endregion 38 39 }
1 /// <summary> 2 3 /// 计算 4 5 /// </summary> 6 7 public interface ICalculator 8 9 { 10 11 /// <summary> 12 13 /// 计算 14 15 /// </summary> 16 17 /// <param name="operation">具体的操作</param> 18 19 /// <param name="operands">操作数</param> 20 21 /// <returns></returns> 22 23 double Operation(IOperation operation, double[] operands); 24 25 }
1 public class Operation:IOperation 2 3 { 4 5 #region IOperation interface implementation 6 7 public string Name 8 9 { 10 11 get; 12 13 private set; 14 15 } 16 17 public string Symbol 18 19 { 20 21 get; 22 23 private set; 24 25 } 26 27 public int NumberOperands 28 29 { 30 31 get; 32 33 private set; 34 35 } 36 37 #endregion 38 39 #region Constructors 40 41 public Operation(string name,string sysmbol,int numberOperands) 42 43 { 44 45 this.Name = name; 46 47 this.Symbol = sysmbol; 48 49 this.NumberOperands = numberOperands; 50 51 } 52 53 #endregion 54 55 }
public sealed class Calculator : ICalculator { #region ICalculator interface implementation public double Operation(IOperation operation, double[] operands) { if (operation==null) { return 0; } switch (operation.Symbol) { case "+": return operands[0] + operands[1]; case "-": return operands[0] - operands[1]; case "*": return operands[0] * operands[1]; case "/": return operands[0] / operands[1]; default: throw new InvalidOperationException(string.Format("invalid operation {0}",operation.Name)); } } #endregion }
客户端程序:
代码如下:
1 class Program 2 3 { 4 5 public ICalculator calculator = new StrategyPatternDemo.Calculator(); 6 7 private IList<IOperation> operationList=new List<IOperation> 8 9 { 10 11 new Operation("加","+",2), 12 13 new Operation("减","-",2), 14 15 new Operation("乘","*",2), 16 17 new Operation("除","/",2), 18 19 }; 20 21 public IEnumerable<IOperation> Operations 22 23 { 24 25 get 26 27 { 28 29 return operationList; 30 31 } 32 33 } 34 35 public void Run() 36 37 { 38 39 var operations = this.Operations; 40 41 var operationsDic = new SortedList<string, IOperation>(); 42 43 foreach (var item in operations) 44 45 { 46 47 Console.WriteLine("操作名称:{0},操作符号:{1},操作个数:{2}", item.Name,item.Symbol, item.NumberOperands); 48 49 operationsDic.Add(item.Symbol, item); 50 51 } 52 53 Console.WriteLine("--------------------------------------------------------------------"); 54 55 string selectedOp = string.Empty; 56 57 do 58 59 { 60 61 try 62 63 { 64 65 Console.Write("输入计算操作符号: "); 66 67 selectedOp = Console.ReadLine(); 68 69 if (selectedOp.ToLower() == "exit" || !operationsDic.ContainsKey(selectedOp)) 70 71 { 72 73 continue; 74 75 } 76 77 var operation = operationsDic[selectedOp]; 78 79 double[] operands = new double[operation.NumberOperands]; 80 81 for (int i = 0; i < operation.NumberOperands; i++) 82 83 { 84 85 Console.Write("\t 第{0}个操作数:", i + 1); 86 87 string selectedOperand = Console.ReadLine(); 88 89 operands[i] = double.Parse(selectedOperand); 90 91 } 92 93 Console.WriteLine("使用计算器"); 94 95 double result = calculator.Operation(operation, operands); 96 97 Console.WriteLine("计算结果:{0}", result); 98 99 Console.WriteLine("--------------------------------------------------------------------"); 100 101 } 102 103 catch (FormatException ex) 104 105 { 106 107 Console.WriteLine(ex.Message); 108 109 Console.WriteLine(); 110 111 continue; 112 113 } 114 115 } while (selectedOp != "exit"); 116 117 } 118 119 static void Main(string[] args) 120 121 { 122 123 var p = new Program(); 124 125 p.Run(); 126 127 } 128 129 }
运行结果如图2:
图2
整体思路是对区分计算操作符号进行switch操作,根据不同的符号进行计算。
4.switch…case…带来的问题
上一小节就带来一个问题,如果我要添加求余计算(计算符号为%),那么必须要修改Calculator类才行,这样就违反了面向对象的开放封闭设计原则。
怎么做呢?怎样才能实现不修改Calculator类就达到扩展的目的呢?
5.使用策略模式重构switch…case…代码
一个解决方案就是使用策略模式重构代码
5.1策略模式的概念
策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。
UML图:
5.2重构方案:
扩展IOperation接口,提供一个计算方法
1 #region 算法 2 3 double Calculator(double[] operands); 4 5 #endregion
1 /// <summary> 2 3 /// 计算操作接口 4 5 /// </summary> 6 7 public interface IOperation 8 9 { 10 11 #region 属性 12 13 /// <summary> 14 15 /// 操作名称 16 17 /// </summary> 18 19 string Name { get; } 20 21 /// <summary> 22 23 /// 操作符号 24 25 /// </summary> 26 27 string Symbol { get; } 28 29 /// <summary> 30 31 /// 操作数量 32 33 /// </summary> 34 35 int NumberOperands { get; } 36 37 #endregion 38 39 #region 算法 40 41 double Calculator(double[] operands); 42 43 #endregion 44 45 }
修改Operation类(相当于UML中的Strategy),实现Calculator方法,修改后的代码如下:
1 public class Operation:IOperation 2 3 { 4 5 #region IOperation interface implementation 6 7 public string Name 8 9 { 10 11 get; 12 13 private set; 14 15 } 16 17 public string Symbol 18 19 { 20 21 get; 22 23 private set; 24 25 } 26 27 public int NumberOperands 28 29 { 30 31 get; 32 33 private set; 34 35 } 36 37 public virtual double Calculator(double[] operands) 38 39 { 40 41 throw new NotImplementedException(); 42 43 } 44 45 protected void CheckOperands(double[] operands) 46 47 { 48 49 if (operands == null) 50 51 { 52 53 throw new ArgumentNullException("operands"); 54 55 } 56 57 if (operands.Length != this.NumberOperands) 58 59 { 60 61 throw new ArgumentException("operands is not equal to NumberOperands"); 62 63 } 64 65 } 66 67 #endregion 68 69 #region Constructors 70 71 public Operation(string name,string sysmbol,int numberOperands) 72 73 { 74 75 this.Name = name; 76 77 this.Symbol = sysmbol; 78 79 this.NumberOperands = numberOperands; 80 81 } 82 83 #endregion
添加加减乘除的具体实现类,分别为:AddOperation、SubOperation、MulOperation、DivOperation
代码如下:
1 /// <summary> 2 3 /// 加法操作 4 5 /// </summary> 6 7 public class AddOperation:Operation 8 9 { 10 11 public AddOperation() : base("加","+",2) 12 13 { 14 15 } 16 17 public override double Calculator(double[] operands) 18 19 { 20 21 base.CheckOperands(operands); 22 23 return operands[0] + operands[1]; 24 25 } 26 27 }
1 /// <summary> 2 3 /// 减法操作 4 5 /// </summary> 6 7 public class SubOperation:Operation 8 9 { 10 11 public SubOperation() : base("减","-",2) { } 12 13 public override double Calculator(double[] operands) 14 15 { 16 17 base.CheckOperands(operands); 18 19 return operands[0] - operands[1]; 20 21 } 22 23 }
1 /// <summary> 2 3 /// 乘法操作 4 5 /// </summary> 6 7 public class MulOperation:Operation 8 9 { 10 11 public MulOperation() : base("乘", "*", 2) { } 12 13 public override double Calculator(double[] operands) 14 15 { 16 17 base.CheckOperands(operands); 18 19 return operands[0] * operands[1]; 20 21 } 22 23 }
/// <summary> /// 除法操作 /// </summary> public class DivOperation:Operation { public DivOperation() : base("除", "/", 2) { } public override double Calculator(double[] operands) { base.CheckOperands(operands); if (operands[1]==0) { throw new ArgumentException("除数不能为0"); } return operands[0] / operands[1]; } }
修改ICalculator接口(相当于UML中的Context),修改后的代码如下:
1 /// <summary> 2 3 /// 计算 4 5 /// </summary> 6 7 public interface ICalculator 8 9 { 10 11 /// <summary> 12 13 /// 计算 14 15 /// </summary> 16 17 /// <param name="operation">具体的操作</param> 18 19 /// <param name="operands">操作数</param> 20 21 /// <returns></returns> 22 23 double Operation(IOperation operation, double[] operands); 24 25 /// <summary> 26 27 /// 策略模式重构需要添加的 28 29 /// </summary> 30 31 /// <param name="operation">计算符号</param> 32 33 /// <param name="operands">操作数</param> 34 35 /// <returns></returns> 36 37 double OperationWithNoSwitch(string operation, double[] operands); 38 39 /// <summary> 40 41 /// 判断操作符号是否存在 42 43 /// </summary> 44 45 /// <param name="operationSymbol"></param> 46 47 /// <returns></returns> 48 49 bool KeyIsExist(string operationSymbol); 50 51 /// <summary> 52 53 /// 根据操作符号获取操作数 54 55 /// </summary> 56 57 /// <param name="operationSymbol"></param> 58 59 /// <returns></returns> 60 61 int OperationNumberOperands(string operationSymbol); 62 63 }
修改Calculator类,实现新增的方法,修改后的代码如下:
1 public sealed class Calculator : ICalculator 2 3 { 4 5 #region Constructors 6 7 public Calculator() 8 9 { 10 11 } 12 13 public Calculator(IEnumerable<IOperation> operations) 14 15 { 16 17 this.operationDic = operations.ToDictionary(u=>u.Symbol); 18 19 } 20 21 #endregion 22 23 #region ICalculator interface implementation 24 25 public double Operation(IOperation operation, double[] operands) 26 27 { 28 29 if (operation==null) 30 31 { 32 33 return 0; 34 35 } 36 37 switch (operation.Symbol) 38 39 { 40 41 case "+": 42 43 return operands[0] + operands[1]; 44 45 case "-": 46 47 return operands[0] - operands[1]; 48 49 case "*": 50 51 return operands[0] * operands[1]; 52 53 case "/": 54 55 return operands[0] / operands[1]; 56 57 default: 58 59 throw new InvalidOperationException(string.Format("invalid operation {0}",operation.Name)); 60 61 } 62 63 } 64 65 #endregion 66 67 #region 策略模式重构需要添加的内容 68 69 private readonly IDictionary<string,IOperation> operationDic; 70 71 public double OperationWithNoSwitch(string operation, double[] operands) 72 73 { 74 75 if (!KeyIsExist(operation)) 76 77 { 78 79 throw new ArgumentException(" operationSysmbol is not exits "); 80 81 } 82 83 return this.operationDic[operation].Calculator(operands); 84 85 } 86 87 public bool KeyIsExist(string operationSymbol) 88 89 { 90 91 return this.operationDic.ContainsKey(operationSymbol); 92 93 } 94 95 public int OperationNumberOperands(string operationSymbol) 96 97 { 98 99 if (!KeyIsExist(operationSymbol)) 100 101 { 102 103 throw new ArgumentException(" operationSysmbol is not exits "); 104 105 } 106 107 return this.operationDic[operationSymbol].NumberOperands; 108 109 } 110 111 #endregion 112 113 }
修改客户端类:
添加 ReconsitutionRun() 方法表示运行重构后的代码
求改后的代码为:
1 class Program 2 3 { 4 5 public ICalculator calculator = new StrategyPatternDemo.Calculator(); 6 7 private IList<IOperation> operationList=new List<IOperation> 8 9 { 10 11 new Operation("加","+",2), 12 13 new Operation("减","-",2), 14 15 new Operation("乘","*",2), 16 17 new Operation("除","/",2), 18 19 }; 20 21 public IEnumerable<IOperation> Operations 22 23 { 24 25 get 26 27 { 28 29 return operationList; 30 31 } 32 33 } 34 35 public void Run() 36 37 { 38 39 var operations = this.Operations; 40 41 var operationsDic = new SortedList<string, IOperation>(); 42 43 foreach (var item in operations) 44 45 { 46 47 Console.WriteLine("操作名称:{0},操作符号:{1},操作个数:{2}", item.Name,item.Symbol, item.NumberOperands); 48 49 operationsDic.Add(item.Symbol, item); 50 51 } 52 53 Console.WriteLine("--------------------------------------------------------------------"); 54 55 string selectedOp = string.Empty; 56 57 do 58 59 { 60 61 try 62 63 { 64 65 Console.Write("输入计算操作符号: "); 66 67 selectedOp = Console.ReadLine(); 68 69 if (selectedOp.ToLower() == "exit" || !operationsDic.ContainsKey(selectedOp)) 70 71 { 72 73 continue; 74 75 } 76 77 var operation = operationsDic[selectedOp]; 78 79 double[] operands = new double[operation.NumberOperands]; 80 81 for (int i = 0; i < operation.NumberOperands; i++) 82 83 { 84 85 Console.Write("\t 第{0}个操作数:", i + 1); 86 87 string selectedOperand = Console.ReadLine(); 88 89 operands[i] = double.Parse(selectedOperand); 90 91 } 92 93 Console.WriteLine("使用计算器"); 94 95 double result = calculator.Operation(operation, operands); 96 97 Console.WriteLine("计算结果:{0}", result); 98 99 Console.WriteLine("--------------------------------------------------------------------"); 100 101 } 102 103 catch (FormatException ex) 104 105 { 106 107 Console.WriteLine(ex.Message); 108 109 Console.WriteLine(); 110 111 continue; 112 113 } 114 115 } while (selectedOp != "exit"); 116 117 } 118 119 /// <summary> 120 121 /// 重构后的代码 122 123 /// </summary> 124 125 IEnumerable<IOperation> operationList2 = new List<IOperation> { 126 127 new AddOperation(), 128 129 new SubOperation(), 130 131 new MulOperation(), 132 133 new DivOperation(), 134 135 }; 136 137 public ICalculator calculator2; 138 139 public void ReconsitutionRun() 140 141 { 142 143 calculator2 = new StrategyPatternDemo.Calculator(operationList2); 144 145 Console.WriteLine("------------------------重构后的执行结果-----------------------------"); 146 147 foreach (var item in operationList2) 148 149 { 150 151 Console.WriteLine("操作名称:{0},操作符号:{1},操作个数:{2}", item.Name, item.Symbol, item.NumberOperands); 152 153 } 154 155 Console.WriteLine("--------------------------------------------------------------------"); 156 157 string selectedOp = string.Empty; 158 159 do 160 161 { 162 163 try 164 165 { 166 167 Console.Write("输入计算操作符号: "); 168 169 selectedOp = Console.ReadLine(); 170 171 if (selectedOp.ToLower() == "exit" || !this.calculator2.KeyIsExist(selectedOp)) 172 173 { 174 175 continue; 176 177 } 178 179 var operandsCount = this.calculator2.OperationNumberOperands(selectedOp); 180 181 double[] operands = new double[operandsCount]; 182 183 for (int i = 0; i < operandsCount; i++) 184 185 { 186 187 Console.Write("\t 第{0}个操作数:", i + 1); 188 189 string selectedOperand = Console.ReadLine(); 190 191 operands[i] = double.Parse(selectedOperand); 192 193 } 194 195 Console.WriteLine("使用计算器"); 196 197 double result = calculator2.OperationWithNoSwitch(selectedOp, operands); 198 199 Console.WriteLine("计算结果:{0}", result); 200 201 Console.WriteLine("--------------------------------------------------------------------"); 202 203 } 204 205 catch (FormatException ex) 206 207 { 208 209 Console.WriteLine(ex.Message); 210 211 Console.WriteLine(); 212 213 continue; 214 215 } 216 217 } while (selectedOp != "exit"); 218 219 } 220 221 static void Main(string[] args) 222 223 { 224 225 var p = new Program(); 226 227 //p.Run(); 228 229 p.ReconsitutionRun(); 230 231 } 232 233 }
重构后的代码执行结果图3:
图3
经过重构后的代码正确运行。
5.3扩展
下面回到上文提到的用switch代码带来的问题一:扩展求余运算。
在Calculator.Client客户端项目中添加一个新类:ModOperation,代码如下:
1 /// <summary> 2 3 /// 求余 4 5 /// </summary> 6 7 public class ModOperation:Operation 8 9 { 10 11 public ModOperation() 12 13 : base("余", "%", 2) 14 15 { } 16 17 public override double Calculator(double[] operands) 18 19 { 20 21 base.CheckOperands(operands); 22 23 return operands[0] % operands[1]; 24 25 } 26 27 }
修改客户端类,将求余运算类添加到上下文中(Calculator)
1 /// <summary> 2 3 /// 重构后的代码 4 5 /// </summary> 6 7 IEnumerable<IOperation> operationList2 = new List<IOperation> { 8 9 new AddOperation(), 10 11 new SubOperation(), 12 13 new MulOperation(), 14 15 new DivOperation(), 16 17 new ModOperation(), 18 19 };
运行结果图4:
图4
经过重构后的代码,可以不修改Calculator类达到扩展算法的目的。
6.总结
通过对实现一个简单计算器的功能来说明了如何使用策略模式重构swith...case...代码,经过重构后的代码可以轻松实现扩展新算法而无需修改原有代码,符合了面向对象的开闭设计原则:对修改关闭,对扩展开放。
在此感谢 圣殿骑士 给我带来的灵感和使用重构的方法,让我对策略模式和重构的认识更进一步。