使用策略模式重构switch case 代码

目录

1.背景

2.案例

3.switch…case…方式实现

4.switch…case…带来的问题

5.使用策略模式重构switch…case…代码

6.总结

1.背景

          之前在看《重构    改善既有代码的设计》一书,在看到Replace Type Code With  State/Strategy(用状态模式/策略模式替换类型码)一节时遇到一个困惑:怎么用策略模式替换switch case代码?所幸的时,两天前查资料的时候偶然看到 圣殿骑士 的博客,他写的《31天重构学习》系列博客让我受益匪浅,也让我领悟到了怎么使用策略模式替换swith  case代码,并在此基础之上编写了一个Demo,以供分享、交流。

2.案例

         功能:简易计算器

         项目结构如图1

       clipboard

                        图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     }
IOperation
 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     }
ICalculator
 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     }
Operation
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

    }
Calculator

 

    客户端程序:

    代码如下:

  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     }
Program

 

    运行结果如图2:

    clipboard[1]

                           图2

 

    整体思路是对区分计算操作符号进行switch操作,根据不同的符号进行计算。

4.switch…case…带来的问题

    上一小节就带来一个问题,如果我要添加求余计算(计算符号为%),那么必须要修改Calculator类才行,这样就违反了面向对象的开放封闭设计原则。

  怎么做呢?怎样才能实现不修改Calculator类就达到扩展的目的呢?

 

5.使用策略模式重构switch…case…代码

    一个解决方案就是使用策略模式重构代码

    5.1策略模式的概念

    策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。

    UML图:

    clipboard[2]

    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     }
IOperation

    修改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
Operation

    添加加减乘除的具体实现类,分别为: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     }
AddOperation
 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     }
SubOperation
 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     }
MulOperation
/// <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];

        }

    }
DivOperation

    修改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     }
ICalculator

    修改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     }
Calculator

    修改客户端类:

    添加 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     }
Program

    重构后的代码执行结果图3:

    clipboard[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     }
ModOperation

    修改客户端类,将求余运算类添加到上下文中(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         };
View Code

         运行结果图4:

       clipboard[4]

                              图4

          经过重构后的代码,可以不修改Calculator类达到扩展算法的目的。

6.总结

         通过对实现一个简单计算器的功能来说明了如何使用策略模式重构swith...case...代码,经过重构后的代码可以轻松实现扩展新算法而无需修改原有代码,符合了面向对象的开闭设计原则:对修改关闭,对扩展开放。

         在此感谢 圣殿骑士 给我带来的灵感和使用重构的方法,让我对策略模式和重构的认识更进一步。

posted @ 2015-02-12 13:33  诸葛小亮  阅读(3675)  评论(0编辑  收藏  举报