18.4 策略模式的扩展
先给出一道小学的题目:输入3个参数,进行加减法运算,参数中两个是int型的,剩下的一个参数是String型的,只有“+”、“-”两个符号可以选择,不要考虑什么复杂的校验,我们做的是白箱测试,输入的就是标准的int类型和合规的String类型,各位大侠,想想看,怎么做,简单得很!
有非常多的实现方式,我今天来说四种。先说第一种,写一个类,然后进行加减法运算,类图也不用画了,太简单了,如代码清单18-11所示。
代码清单18-11 最直接的加减法
public class Calculator {
//加符号
private final static String ADD_SYMBOL = "+";
//减符号
private final static String SUB_SYMBOL = "-";
public int exec(int a,int b,String symbol){
int result =0;
if(symbol.equals(ADD_SYMBOL)){
result = this.add(a, b);
}else if(symbol.equals(SUB_SYMBOL)){
result = this.sub(a, b);
}
return result;
}
//加法运算
private int add(int a,int b){
return a+b;
}
//减法运算
private int sub(int a,int b){
return a-b;
}
}
算法太简单了,每个程序员都会写。再写一个场景类如18-12所示。
代码清单18-12 场景类
public class Client {
public static void main(String[] args) {
//输入的两个参数是数字
int a = Integer.parseInt(args[0]);
String symbol = args[1]; //符号
int b = Integer.parseInt(args[2]);
System.out.println("输入的参数为:"+Arrays.toString(args));
//生成一个运算器
Calculator cal = new Calculator();
System.out.println("运行结果为:"+a + symbol + b + "=" + cal.exec(a, b, symbol));
}
}
输入3个参数,分别是100 + 200,运行结果如下所示:
输入的参数为:[100, +, 200]
运行结果为:100+200=300
这个方案是非常简单的,能够解决问题,我相信这是大家最容易想到的方案,我们不评论这个方案的优劣,等把四个方案全部讲完了,你自己就会发现孰优孰劣。
我们再来看第二个方案,Calculator类太嗦了,简化算法如代码清单18-13所示。
代码清单18-13 简化算法
public class Calculator {
//加符号
private final static String ADD_SYMBOL = "+";
//减符号
private final static String SUB_SYMBOL = "-";
public int exec(int a,int b,String symbol){
return symbol.equals(ADD_SYMBOL)?a+b:a-b;
}
}
这也非常简单,就是一个三目运算符,确实简化了很多。有缺陷先别管,我们主要讲设计,你在实际项目应用中要处理该程序中的缺陷。
该方案的场景类与方案一相同,如代码清单18-12所示,运行结果也相同,不再赘述。
我们再来思考第三个方案,本章介绍策略模式,那把策略模式应用到该需求是不是很合适啊?是的,非常合适!加减法就是一个具体的策略,非常简单,省略类图,直接看源码,我们先来看抽象策略,定义每个策略必须实现的方法,如代码清单18-14所示。
代码清单18-14 引入策略模式
interface Calculator {
public int exec(int a,int b);
}
抽象策略定义了一个唯一的方法来执行运算。至于具体执行的是加法还是减法,运算时由上下文角色决定。我们再来看两个具体的策略,如代码清单18-15所示。
代码清单18-15 具体策略
public class Add implements Calculator {
//加法运算
public int exec(int a, int b) {
return a+b;
}
}
public class Sub implements Calculator {
//减法运算
public int exec(int a, int b) {
return a-b;
}
}
封装角色的责任是保证策略时可以相互替换,如代码清单18-15所示。
代码清单18-16 上下文
public class Context {
private Calculator cal = null;
public Context(Calculator _cal){
this.cal = _cal;
}
public int exec(int a,int b,String symbol){
return this.cal.exec(a, b);
}
}
代码都非常简单,该部分就不再增加注释信息了。上下文类负责把策略封装起来,具体怎么自由地切换策略则是由高层模块负责声明的,如代码清单18-17所示。
代码清单18-17 场景类
public class Client {
//加符号
public final static String ADD_SYMBOL = "+";
//减符号
public final static String SUB_SYMBOL = "-";
public static void main(String[] args) {
//输入的两个参数是数字
int a = Integer.parseInt(args[0]);
String symbol = args[1]; //符号
int b = Integer.parseInt(args[2]);
System.out.println("输入的参数为:"+Arrays.toString(args));
//上下文
Context context = null;
//判断初始化哪一个策略
if(symbol.equals(ADD_SYMBOL)){
context = new Context(new Add());
}else if(symbol.equals(SUB_SYMBOL)){
context = new Context(new Sub());
}
System.out.println("运行结果为:"+a+symbol+b+"="+context.exec(a,b,symbol));
}
}
运行结果与方案一相同。我们想想看,在该策略模式的一个具体应用中,我们使用Context准备了一组算法(加法和减法),并封装了起来,具体使用哪一个策略(加法还是减法)则由上层模块声明,这样扩展性非常好。
现在只剩最后一个方案了,一般最后出场的都是重量级的人物,压场嘛!那就请出我们最后一个重量级角色,音乐响起,一个黑影站定舞台中央,所有灯光突然聚焦,主角缓缓抬起头,它就是——策略枚举!我们来看看其真实实力,如代码清单18-18所示。
代码清单18-18 策略枚举
public enum Calculator {
//加法运算
ADD("+"){
public int exec(int a,int b){
return a+b;
}
},
//减法运算
SUB("-"){
public int exec(int a,int b){
return a - b;
}
};
String value = "";
//定义成员值类型
private Calculator(String _value){
this.value = _value;
}
//获得枚举成员的值
public String getValue(){
return this.value;
}
//声明一个抽象函数
public abstract int exec(int a,int b);
}
先想一想它的名字,为什么叫做策略枚举?枚举没有问题,它就是一个Enum类型,那为什么又叫做策略呢?找找看能不能找到策略的影子在里面?是的,我们定义了一个抽象的方法exec,然后在每个枚举成员中进行了实现,如果不实现会怎么样呢?你试试看看,不实现该方法就不能编译,现在是不是清楚了?把原有定义在抽象策略中的方法移植到枚举中,每个枚举成员就成为一个具体策略。简单吧,总结一下,策略枚举定义如下:
● 它是一个枚举。
● 它是一个浓缩了的策略模式的枚举。
当然,读者可能要反思了,我使用内置类也可以实现相同的功能,写一个Context类,然后把抽象策略、具体策略都内置进去,不就可以解决问题了,是的,可以解决,但是扩展性如何?可读性如何?代码是让人读的,然后才是让机器执行,别把顺序搞反了!
我们继续完善方案四,场景类稍有改动,如代码清单18-19所示。
代码清单18-19 场景类
public class Client {
public static void main(String[] args) {
//输入的两个参数是数字
int a = Integer.parseInt(args[0]);
String symbol = args[1]; //符号
int b = Integer.parseInt(args[2]);
System.out.println("输入的参数为:"+Arrays.toString(args));
System.out.println("运行结果为:"+a+symbol+b+"="+Calculator.ADD.exec(a,b));
}
}
运行结果与方案一相同。看这个场景类,代码量非常少,而且还有一个显著的优点:真实地面向对象,看看这条语句:
Calculator.ADD.exec(a, b)
是不是类似于“拿出计算器(Calculator),对a和b进行加法运算(ADD),并立刻执行(exec)”,这与我们日常接触逻辑是不是非常相似,这也正是我们架构师要担当的职责!
注意 策略枚举是一个非常优秀和方便的模式,但是它受枚举类型的限制,每个枚举项都是public、final、static的,扩展性受到了一定的约束,因此在系统开发中,策略枚举一般担当不经常发生变化的角色。
- 前言
- 第一部分 大旗不挥,谁敢冲锋——6大设计原则全新解读
- 第1章 单一职责原则
- 1.2 绝杀技,打破你的传统思维
- 1.3 我单纯,所以我快乐
- 1.4 最佳实践
- 第2章 里氏替换原则
- 2.2 纠纷不断,规则压制
- 2.3 最佳实践
- 第3章 依赖倒置原则
- 3.2 言而无信,你太需要契约
- 3.3 依赖的三种写法
- 3.4 最佳实践
- 第4章 接口隔离原则
- 4.2 美女何其多,观点各不同
- 4.3 保证接口的纯洁性
- 4.4 最佳实践
- 第5章 迪米特法则
- 5.2 我的知识你知道得越少越好
- 5.3 最佳实践
- 第6章 开闭原则
- 6.2 开闭原则的庐山真面目
- 6.3 为什么要采用开闭原则
- 6.4 如何使用开闭原则
- 6.5 最佳实践
- 第二部分 真刀实枪 ——23种设计模式完美演绎
- 第7章 单例模式
- 7.2 单例模式的定义
- 7.3 单例模式的应用
- 7.4 单例模式的扩展
- 7.5 最佳实践
- 第8章 工厂方法模式
- 8.2 工厂方法模式的定义
- 8.3 工厂方法模式的应用
- 8.4 工厂方法模式的扩展
- 8.5 最佳实践
- 第9章 抽象工厂模式
- 9.2 抽象工厂模式的定义
- 9.3 抽象工厂模式的应用
- 9.4 最佳实践
- 第10章 模板方法模式
- 10.2 模板方法模式的定义
- 10.3 模板方法模式的应用
- 10.4 模板方法模式的扩展
- 10.5 最佳实践
- 第11章 建造者模式
- 11.2 建造者模式的定义
- 11.3 建造者模式的应用
- 11.4 建造者模式的扩展
- 11.5 最佳实践
- 第12章 代理模式
- 12.2 代理模式的定义
- 12.3 代理模式的应用
- 12.4 代理模式的扩展
- 12.5 最佳实践
- 第13章 原型模式
- 13.2 原型模式的定义
- 13.3 原型模式的应用
- 13.4 原型模式的注意事项
- 13.5 最佳实践
- 第14章 中介者模式
- 14.2 中介者模式的定义
- 14.3 中介者模式的应用
- 14.4 中介者模式的实际应用
- 14.5 最佳实践
- 第15章 命令模式
- 15.2 命令模式的定义
- 15.3 命令模式的应用
- 15.4 命令模式的扩展
- 15.5 最佳实践
- 第16章 责任链模式
- 16.2 责任链模式的定义
- 16.3 责任链模式的应用
- 16.4 最佳实践
- 第17章 装饰模式
- 17.2 装饰模式的定义
- 17.3 装饰模式应用
- 17.4 最佳实践
- 第18章 策略模式
- 18.2 策略模式的定义
- 18.3 策略模式的应用
- 18.4 策略模式的扩展
- 18.5 最佳实践
- 第19章 适配器模式
- 19.2 适配器模式的定义
- 19.3 适配器模式的应用
- 19.4 适配器模式的扩展
- 19.5 最佳实践
- 第20章 迭代器模式
- 20.2 迭代器模式的定义
- 20.3 迭代器模式的应用
- 20.4 最佳实践
- 第21章 组合模式
- 21.2 组合模式的定义
- 21.3 组合模式的应用
- 21.4 组合模式的扩展
- 21.5 最佳实践
- 第22章 观察者模式
- 22.2 观察者模式的定义
- 22.3 观察者模式的应用
- 22.4 观察者模式的扩展
- 22.5 最佳实践
- 第23章 门面模式
- 23.2 门面模式的定义
- 23.3 门面模式的应用
- 23.4 门面模式的注意事项
- 23.5 最佳实践
- 第24章 备忘录模式
- 24.2 备忘录模式的定义
- 24.3 备忘录模式的应用
- 24.4 备忘录模式的扩展
- 24.5 最佳实践
- 第25章 访问者模式
- 25.2 访问者模式的定义
- 25.3 访问者模式的应用
- 25.4 访问者模式的扩展
- 25.5 最佳实践
- 第26章 状态模式
- 26.2 状态模式的定义
- 26.3 状态模式的应用
- 第27章 解释器模式
- 27.2 解释器模式的定义
- 27.3 解释器模式的应用
- 27.4 最佳实践
- 第28章 享元模式
- 28.2 享元模式的定义
- 28.3 享元模式的应用
- 28.4 享元模式的扩展
- 28.5 最佳实践
- 第29章 桥梁模式
- 29.2 桥梁模式的定义
- 29.3 桥梁模式的应用
- 29.4 最佳实践
- 第三部分 谁的地盘谁做主 ——设计模式PK
- 第30章 创建类模式大PK
- 30.1 工厂方法模式VS建造者模式
- 30.2 抽象工厂模式VS建造者模式
- 第31章 结构类模式大PK
- 31.1 代理模式VS装饰模式
- 31.2 装饰模式VS适配器模式
- 第32章 行为类模式大PK
- 32.1 命令模式VS策略模式
- 32.2 策略模式VS状态模式
- 32.3 观察者模式VS责任链模式
- 第33章 跨战区PK
- 33.1 策略模式VS桥梁模式
- 33.2 门面模式VS中介者模式
- 33.3 包装模式群PK
- 第四部分 完美世界 ——设计模式混编
- 第34章 命令模式+责任链模式
- 34.2 混编小结
- 第35章 工厂方法模式+策略模式
- 35.2 混编小结
- 第36章 观察者模式+中介者模式
- 36.2 混编小结
- 第五部分 扩展篇
- 第37章 MVC框架
- 37.2 最佳实践
- 第38章 新模式
- 38.1 规格模式
- 38.2 对象池模式
- 38.3 雇工模式
- 38.4 黑板模式
- 38.5 空对象模式
- 附录 23种设计模式彩图