首页 » 设计模式之禅(第2版) » 设计模式之禅(第2版)全文在线阅读

《设计模式之禅(第2版)》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的,扩展性受到了一定的约束,因此在系统开发中,策略枚举一般担当不经常发生变化的角色。