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

时间:2022-04-11 09:46:30

目录

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

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

图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:

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

图2

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

4.switch…case…带来的问题

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

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

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

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

    5.1策略模式的概念

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

    UML图:

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

    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:

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

图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:

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

图4

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

6.总结

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

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