使用策略模式重构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两个实现类。
具体实现代码如下:
/// <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...代码,经过重构后的代码可以轻松实现扩展新算法而无需修改原有代码,符合了面向对象的开闭设计原则:对修改关闭,对扩展开放。
在此感谢 圣殿骑士 给我带来的灵感和使用重构的方法,让我对策略模式和重构的认识更进一步。
使用策略模式重构switch case 代码的更多相关文章
- Android Studio-设置switch/case代码块自动补齐
相信很多和我一样的小伙伴刚从Eclipse转到Android Studio的时候,一定被快捷键给搞得头晕了,像Eclipse中代码补齐的快捷键是Alt+/ ,但是在AS中却要自己设置,这还不是问题的关 ...
- 使用策略者模式减少switch case 语句
策略者模式 很简单的一个定义:抽象策略(Strategy)类:定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,环境角色使用这个接口调用不同的算法,一般使用接口或抽象类实现. 场景 在这之前 ...
- 策略模式在PHP业务代码的实践
[大话设计模式]-- 策略者模式(Strategy):它定义了算法家族,分别封装起来,让他们之间可以互相替换,此模式让算法的变法,不会影响到使用算法的客户. 策略模式的核心就是屏蔽内部策略算法,内部的 ...
- 策略模式原理及Java代码实例
一.策略模式的定义 —— 定义了一组算法,将每个算法包装起来,并且使它们之间可以互换 —— 策略模式使这些算法在客户端调用它们的时候能够相互不影响的变化,改变不同算法的实现方式不影响客户端的使用,即策 ...
- 策略模式+注解 干掉业务代码中冗余的if else...
前言: 之前写过一个工作中常见升级模式-策略模式 的文章,里面讲了具体是怎样使用策略模式去抽象现实中的业务代码,今天来拿出实际代码来写个demo,这里做个整理来加深自己对策略模式的理解. 一.业务 ...
- C语言宏定义##连接符和#符的使用(MFC就是靠##自动把消息和消息函数对应起来了,借助宏来减少switch case代码的编写量)
C语言中如何使用宏C(和C++)中的宏(Macro)属于编译器预处理的范畴,属于编译期概念(而非运行期概念).下面对常遇到的宏的使用问题做了简单总结. 关于#和## 在C语言的宏中,#的功能是将其后面 ...
- JavaScript设计模式之策略模式
所谓"条条道路通罗马",在现实中,为达到某种目的往往不是只有一种方法.比如挣钱养家:可以做点小生意,可以打分工,甚至还可以是偷.抢.赌等等各种手段.在程序语言设计中,也会遇到这种类 ...
- JavaScript设计模式 Item 7 --策略模式Strategy
1.策略模式的定义 何为策略?比如我们要去某个地方旅游,可以根据具体的实际情况来选择出行的线路. 如果没有时间但是不在乎钱,可以选择坐飞机. 如果没有钱,可以选择坐大巴或者火车. 如果再穷一点,可以选 ...
- JavaScript设计模式(策略模式)
策略模式的定义是:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换.将不变的部分和变化的部分隔开是每个设计模式的主题,策略模式也不例外,策略模式的目的就是将算法的使用与算法的实现分离开来 ...
随机推荐
- 如何下载Vimeo视频
MediaHuman YouTube Downloader是应用在Mac上的一款非常优秀的YouTube视频下载工具,YouTube Downloader破解版将帮助你快速完成视频下载,而不会挂断.您 ...
- 常用数据结构之ArrayList
前言 ArrayList想必是广大Java程序员开发时最常用的数据结构了,但不一定对其原理都有了解,今天我将结合ArrayList的源码对其进行讲解.本文将围绕ArrayList主要特性(包括适用场景 ...
- php使用微信登录
1.第一步 $hosturl = urlencode('');//异步回调地址 $wechatInfo = WechatInfo::get_wechat(); //查询appid $url = &qu ...
- tp5.1 无限极分类前台展示
商城前台的多级分类展示,如图所示,一般是三级.所以代码只组装到第三级. 数据库设计,共5个字段.level字段是方便用来显示级别的.pid是父级分类id,pid等于0说明它是一级分类.path字段,举 ...
- 暴力破解( Hydra | Medusa)
暴力破解 By : Mirror王宇阳 笔者告知 : 暴力破解的结果是运气和速度的结晶,开始暴力破解前烧一炷香也是必要的! 引用张炳帅的一句话:"你的运气和管理员的安全意识成正比" ...
- 部署flas到服务器:No module named flask
首先,你要先把nginx和uwsgi安装好(个人觉得这搭起来比较舒服),可以通过pip 或者源安装,具体方法在前面我有提到过,好了接下来我就讲讲我的踩坑经历与解决办法. 我先采用的pip insta ...
- ruby中的数组相关方法介绍
l = ["a","b","c","d","e","f",'g'] puts l ...
- IPFS学习-分布式哈希表DHT
Distributed Hash Tables(DHT) 分布式哈希表是一个分布式的键值对存储结构.在IPFS网络中,每一个节点都维护一个DHT的子集.当节点接受到一个请求.该节点要么直接回复,要么通 ...
- SourceTree Mac安装跳过注册步骤
1.打开sourcetree2.关闭sourcetree3.命令终端输入defaults write com.torusknot.SourceTreeNotMAS completedWelcomeWi ...
- 【Objective-C】Objective-C语言的动态性
Objective-C语言的动态性主要体现在以下3个方面 (1)动态类型:运行时确定对象的类型. (2)动态绑定:运行时确定对象的方法. (3)动态加载:运行时加载需要的资源或者或代码模块. 一.动态 ...


