目錄算法
1.背景ide
2.案例學習
6.總結設計
1.背景3d
以前在看《重構 改善既有代碼的設計》一書,在看到Replace Type Code With State/Strategy(用狀態模式/策略模式替換類型碼)一節時遇到一個困惑:怎麼用策略模式替換switch case代碼?所幸的時,兩天前查資料的時候偶然看到 聖殿騎士 的博客,他寫的《31天重構學習》系列博客讓我受益不淺,也讓我領悟到了怎麼使用策略模式替換swith case代碼,並在此基礎之上編寫了一個Demo,以供分享、交流。code
2.案例orm
功能:簡易計算器
項目結構如圖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...代碼,通過重構後的代碼能夠輕鬆實現擴展新算法而無需修改原有代碼,符合了面向對象的開閉設計原則:對修改關閉,對擴展開放。
在此感謝 聖殿騎士 給我帶來的靈感和使用重構的方法,讓我對策略模式和重構的認識更進一步。