关键词:
策略模式,需要我们结合简单工厂模式,更高级地用法可能需要我们掌握Java反射机制。简单工厂模式我们在最早的时候介绍,我们也谈到了一点Java的反射机制。借着学习策略模式的机会,我们顺便复习一下简单工厂模式和反射。
先说说何为策略模式。“策略”我的理解是,对一件事,有不同的方法去做,至于用何种方法取决于我们的选择。我们同样借助《大话设计模式》中实现策略模式的例子来做讲解。
超市进场做活动,我们现在假设有正常不减价、打折、满减这三种活动,这正是对“买东西收费”这件事,有三种不同的“方法”,这三种方法其实就是三种不同的算法。我们定义出策略模式:它定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化不会影响到使用算法的客户。看到这个可能还是一脸茫然,不着急我们一步一步来这句话到底想表达什么意思。
首先,对于正常不减价,我们可以直接计算返回该收取的金额为多少。对于打折的这种情况,我们可能会想到传递一个“打多少折”的参数进去,计算返回该收取的金额为多少。对于满减的这种情况,我们传递两个参数,“返利条件”及“返多少利”,计算返回该收取的金额为多少。那么它们似乎都有一个公共方法,对于应收金额,返回实收金额。我们可以将三种情况抽取为一个接口或抽象类。来试着画出UML类图结构。
看到UML的类结构图,我们其实可以联想到简单工厂模式,如果我们就这样来写,在客户端就需要来具体实例化哪一个类。我们不想在客户端来做出判断决定来实例化哪一个类,这个时候怎么办呢——简单工厂模式可以帮我们实现。客户端不决定具体实例化哪一个类,而是交由“工厂”来帮我们实例化。所以其实我们首先是实现的一个“简单工厂模式”。
所以我们上面的UML类结构图就可以做下修改。
接下来写出我们的代码。
1 package day_20_cash; 2 3 /** 4 * 收费接口 5 * @author turbo 6 * 7 * 2016年9月20日 8 */ 9 public interface CashSuper { 10 /** 11 * 计算实收的费用 12 * @param money 应收金额 13 * @return 实收金额 14 */ 15 double acceptCash(double money); 16 }
1 package day_20_cash; 2 3 /** 4 * 正常收费 5 * @author turbo 6 * 7 * 2016年9月20日 8 */ 9 public class CashNormal implements CashSuper { 10 11 /* (non-Javadoc) 12 * @see day_20_cash.CashSuper#acceptCash(double) 13 */ 14 @Override 15 public double acceptCash(double money) { 16 17 return money; 18 } 19 20 }
1 package day_20_cash; 2 3 /** 4 * 打折 5 * @author turbo 6 * 7 * 2016年9月20日 8 */ 9 public class CashRebate implements CashSuper { 10 private double moneyRebate; 11 12 13 /** 14 * @param moneyRebate 折扣率 15 */ 16 public CashRebate(double moneyRebate) { 17 this.moneyRebate = moneyRebate; 18 } 19 20 21 /* (non-Javadoc) 22 * @see day_20_cash.CashSuper#acceptCash(double) 23 */ 24 @Override 25 public double acceptCash(double money) { 26 27 return money * (moneyRebate / 10); 28 } 29 30 }
1 package day_20_cash; 2 3 /** 4 * 满减 5 * @author turbo 6 * 7 * 2016年9月20日 8 */ 9 public class CashReturn implements CashSuper { 10 private double moneyCondition; //应收金额 11 private double moneyReturn; //返利金额 12 13 public CashReturn(double moneyCondition, double moneyReturn){ 14 this.moneyCondition = moneyCondition; 15 this.moneyReturn = moneyReturn; 16 } 17 /* (non-Javadoc) 18 * @see day_20_cash.CashSuper#acceptCash(double) 19 */ 20 @Override 21 public double acceptCash(double money) { 22 if (money >= moneyCondition){ 23 money = money - moneyReturn; 24 } 25 return money; 26 } 27 28 }
1 package day_20_cash; 2 3 /** 4 * 收费对象生成工厂 5 * @author turbo 6 * 7 * 2016年9月20日 8 */ 9 public class CashFactory { 10 public static CashSuper createCashAccept(String cashType){ 11 CashSuper cs = null; 12 switch (cashType) { 13 case "正常收费" : 14 cs = new CashNormal(); 15 break; 16 case "打8折" : 17 cs = new CashRebate(8); 18 break; 19 case "满300减100" : 20 cs = new CashReturn(300, 100); 21 break; 22 default : 23 break; 24 } 25 26 return cs; 27 } 28 }
1 package day_20_cash; 2 3 /** 4 * 客户端抽象代码 5 * @author turbo 6 * 7 * 2016年9月20日 8 */ 9 public class Main { 10 11 /** 12 * @param args 13 */ 14 public static void main(String[] args) { 15 CashSuper cs = CashFactory.createCashAccept("打8折"); 16 double result = cs.acceptCash(300); 17 System.out.println(result); 18 } 19 20 }
这样虽然在客户端中,我们不用关系具体实体化哪一个类,但这同样也带来一定的问题,如果我们要打7折呢?我们是否要在工厂类中新增一个case?那满500减100呢?商场的活动经常在改变,如果真向我们现在所写的这样未免有些牵强,我们要不断地去修改工厂类,不断地重新编译重新部署。面对算法的时常变动,我们可以选择策略模式。
对于策略模式,我们需要引入一个CashContext类,这个类用于维护对Strategy对象的引用。还是太抽象,我们从代码的角度来看,CashContext是一个什么类。(上面的CashSuper及其实现类不用修改)
1 package day_20_cash; 2 3 /** 4 * Context上下文,维护对strategy对象的引用 5 * @author turbo 6 * 7 * 2016年9月21日 8 */ 9 public class CashContext { 10 CashSuper cs = null; 11 public CashContext(CashSuper csuper){ 12 this.cs = csuper; 13 } 14 15 public double getResult(double money){ 16 17 return cs.acceptCash(money); 18 } 19 }
再来看客户端代码怎么写。
1 package day_20_cash; 2 3 /** 4 * 客户端抽象代码 5 * @author turbo 6 * 7 * 2016年9月20日 8 */ 9 public class Main { 10 11 /** 12 * @param args 13 */ 14 public static void main(String[] args) { 15 CashContext context = null; 16 double money = 0.0; 17 String strategy = "打8折"; 18 switch (strategy) { 19 case "正常收费" : 20 context = new CashContext(new CashNormal()); 21 break; 22 case "打8折" : 23 context = new CashContext(new CashRebate(8)); 24 break; 25 case "满300减100" : 26 context = new CashContext(new CashReturn(300, 100)); 27 break; 28 29 default : 30 break; 31 } 32 33 money = context.getResult(300); 34 System.out.println(money); 35 } 36 37 }
这样我们就实现了策略模式。
但是,我们又再一次客户端做了判断,实际上我们似乎是将switch语句从工厂移到了客户端,这不又违背我们的初衷回到原点了吗?那我们是否能将switch“又移到”工厂中去呢?换句话说,策略模式和工厂模式相结合。
我们改进CashContext在其中实现简单工厂。
1 package day_20_cash; 2 3 /** 4 * Context上下文,维护对strategy对象的引用 5 * @author turbo 6 * 7 * 2016年9月21日 8 */ 9 public class CashContext { 10 CashSuper cs = null; 11 public CashContext(String type){ 12 switch (type) { 13 case "正常收费" : 14 CashNormal normal = new CashNormal(); 15 cs = normal; 16 break; 17 case "满300减100" : 18 CashReturn returnx = new CashReturn(300, 100); 19 cs = returnx; 20 case "打8折" : 21 CashRebate rebate = new CashRebate(8); 22 cs = rebate; 23 default : 24 break; 25 } 26 } 27 28 public double getResult(double money){ 29 30 return cs.acceptCash(money); 31 } 32 }
客户端测试代码:
1 package day_20_cash; 2 3 /** 4 * 客户端抽象代码 5 * @author turbo 6 * 7 * 2016年9月21日 8 */ 9 public class Main { 10 11 /** 12 * @param args 13 */ 14 public static void main(String[] args) { 15 CashContext context = null; 16 double money = 0.0; 17 String strategy = "打8折"; 18 context = new CashContext(strategy); 19 money = context.getResult(300); 20 System.out.println(money); 21 } 22 23 }
从代码角度来看,不就是把switch从Main客户端类移到了CashContext类嘛,好像根本没什么用啊。我们用书里的解释吧,“简单工厂模式需要让客户端认识两个类,CashSuper和CashFactory,而策略模式与简单工厂结合的用法,客户端就只需要认识一个类CashContext就可以了。耦合更加降低。”“我们在客户端实例化的是CashContext的对象,调用的是CashContext的方法getResult,这使得具体的收费算法彻底地与客户端分离。连算法的父类CashSuper都不让客户端认识了。”
在这里我们要领会“客户端”带来的含义是什么,在这里我们就是写的一个main函数,“客户端”在编码过程中,我们可以把它想象理解为调用方。调用方如果引用多个类是不是带来很大的耦合性?但如果只引用一个类,那是不是只需要维护这个类的引用即可?这也就是我们常说的解耦。
下面我们来实现在最开始提到的使用“反射”来去掉switch判断语句,可以先自己思考一下试着自己写出来。这里可以参考一下之前涉及到一点反射的博文,《初识Java反射》,《工厂模式——抽象工厂模式(+反射)》。
修改CashContext类,利用反射消除Switch判断语句:
1 package day_20_cash; 2 3 import java.lang.reflect.Constructor; 4 import java.lang.reflect.InvocationTargetException; 5 6 /** 7 * Context上下文,维护对strategy对象的引用 8 * @author turbo 9 * 10 * 2016年9月22日 11 */ 12 public class CashContext { 13 Class<?> clazz = null; 14 Object obj = null; 15 public CashContext(String className, Class[] paramsType, Object[] parmas){ 16 try { 17 clazz = Class.forName(className); 18 Constructor con = clazz.getConstructor(paramsType); 19 obj = con.newInstance(parmas); 20 } catch (InstantiationException | IllegalAccessException e) { 21 e.printStackTrace(); 22 } catch (ClassNotFoundException e) { 23 e.printStackTrace(); 24 } catch (IllegalArgumentException e) { 25 e.printStackTrace(); 26 } catch (InvocationTargetException e) { 27 e.printStackTrace(); 28 } catch (NoSuchMethodException e) { 29 e.printStackTrace(); 30 } catch (SecurityException e) { 31 e.printStackTrace(); 32 } 33 34 } 35 36 public double getResult(double money){ 37 38 return ((CashSuper)obj).acceptCash(money); 39 } 40 }
修改客户端测试代码:
1 package day_20_cash; 2 3 /** 4 * 客户端测试代码 5 * @author turbo 6 * 7 * 2016年9月22日 8 */ 9 public class Main { 10 11 /** 12 * @param args 13 */ 14 public static void main(String[] args) { 15 CashContext context = null; 16 double money = 0.0; 17 String type = "day_20_cash.CashRebate"; 18 Class[] paramTypes = {double.class}; //注意在这里不能使用double的引用类型Double,我猜测是这样涉及一点自动装箱和拆箱 19 Object[] params = {8.0}; 20 context = new CashContext(type, paramTypes, params); 21 money = context.getResult(300); 22 System.out.println(money); 23 } 24 25 }
至于为什么要用到反射来消除switch,在上面两篇博文中已经有提到过,这里不再叙述。其实在客户端测试代码中,我们还可以进一步把代码写得再优美一点。
java设计模式---策略模式(案例解析)
策略模式一、概念1、理解策略模式策略模式是一种行为型模式,它将对象和行为分开,将行为定义为一个行为接口和具体行为的实现。策略模式最大的特点是行为的变化,行为之间可以相互替换。每个if判断都可以理解为就是一... 查看详情
策略模式
1策略模式实际上就是应对有各种策略(实现方法)的需求。比如超市促销,有多种多样的促销策略,这时候就要想到策略模式,策略模式一般会配合工厂模式使用。代码如下:packagecom.biao.strategy.original;/****@authorbiao*策略模式:... 查看详情
代码片-策略模式+工厂模式(代码片段)
通过策略类实现不同场景的策略处理,通过工厂模式创建不同的策略对象1.策略实现接口、策略实现类1.1策略接口/***策略接口*/publicinterfaceIWarnRulepublicvoidwarn();1.2策略实现类/***防拆告警策略实现类*/publicclassAntiRemovalWarnimplements... 查看详情
代码片-策略模式+工厂模式(代码片段)
通过策略类实现不同场景的策略处理,通过工厂模式创建不同的策略对象1.策略实现接口、策略实现类1.1策略接口/***策略接口*/publicinterfaceIWarnRulepublicvoidwarn();1.2策略实现类/***防拆告警策略实现类*/publicclassAntiRemovalWarnimplements... 查看详情
代码片-策略模式+工厂模式(代码片段)
通过策略类实现不同场景的策略处理,通过工厂模式创建不同的策略对象1.策略实现接口、策略实现类1.1策略接口/***策略接口*/publicinterfaceIWarnRulepublicvoidwarn();1.2策略实现类/***防拆告警策略实现类*/publicclassAntiRemovalWarnimplements... 查看详情
设计模式之策略模式
策略模式属于对象行为型的设计模式定义:封装了一些列算法,它们之前可以相互替换,此模式使得算法的改变,不会影响到使用它们的客户端 策略模式有以下3个角色组成抽象策略类:所有策略类的父类,为所支持的... 查看详情
设计模式-策略模式
策略模式(strategypattern)策略模式(strategypattern):策略模式定义了算法族,分别封装起来,让它们之间可以相互替换,此模式让算法的变化独立于使用算法的客户。策略模式是针对一组算法,将每个算法封装到具有公共接口的独立的... 查看详情
设计模式——策略模式
策略模式的重点并不是策略的实现,而是策略的管理的使用!应用场景:当同一种职责,有不同的实现方式的时候可以使用策略管理;角色:1、策略抽象类 2、策略的具体实现 3、策略环境(此类的作用在代... 查看详情
《设计模式之禅》之策略模式
一、策略模式的定义策略模式是一种比较简单的模式,也叫做政策模式,其定义如下:定义一组算法,将每个算法都封装起来,并且使它们之间可以互换。策略模式使用的是面向对象的继承和多态机制,我们看看策略模式的三个... 查看详情
设计模式中的多态——策略模式详解(代码片段)
目录1.关于策略模式2.策略模式详解2.1策略模式定义2.2策略模式的UML类图3.策略模式的优点3.1一个使用策略模式的例子3.2与其他实现方式的对比3.3使用策略模式的优点4.使用工厂方法模式改进原有策略模式5.总结5.参考资料1.关于策... 查看详情
策略模式与工厂模式实践(代码片段)
hello,大家好,我是聪聪。文章目录1.介绍2.策略模式结构2.1分支逻辑解释2.2策略模式设计2.3策略模式代码2.3.1通用策略接口及其各策略实现。2.3.2执行上下文信息2.3.3客户端调用2.3.4总结3.常用示例3.1定义一个策略枚举3.2策... 查看详情
策略模式与工厂模式实践(代码片段)
hello,大家好,我是聪聪。文章目录1.介绍2.策略模式结构2.1分支逻辑解释2.2策略模式设计2.3策略模式代码2.3.1通用策略接口及其各策略实现。2.3.2执行上下文信息2.3.3客户端调用2.3.4总结3.常用示例3.1定义一个策略枚举3.2策... 查看详情
策略模式与工厂模式实践(代码片段)
hello,大家好,我是聪聪。文章目录1.介绍2.策略模式结构2.1分支逻辑解释2.2策略模式设计2.3策略模式代码2.3.1通用策略接口及其各策略实现。2.3.2执行上下文信息2.3.3客户端调用2.3.4总结3.常用示例3.1定义一个策略枚举3.2策... 查看详情
策略模式
策略模式策略模式的用意是针对一组算法,将每一个算法封装到具有共同接口的独立类中,从而使它们可以相互替换。策略模式是对算法的包装,是把算法使用和算法本身分开。策略模式通常是把一系列算法包装到一系列的策略... 查看详情
设计模式笔记-策略模式(代码片段)
设计模式笔记-策略模式文章目录设计模式笔记-策略模式1.策略模式2.常见写法3.常见框架中的策略模式1.策略模式策略模式(Strategy),定义了一组算法,将每个算法都封装起来,并且使它们之间可以互换,在使... 查看详情
策略模式
...在于他们的行为的不同一个系统需要动态地在几种算法(策略)中选择一种 三、优点1、开闭原则2、避免使用多种条件转移语句if..else3、提高算法的保密性和安全性 四、缺点1、客户端必须知道所有的策略类,并自行决定... 查看详情
策略模式
琢磨了一下策略模式,发现各个模式之间并非区别特别大,他们都是基于软件project一个大的基础上进行细微化区别的设计。要想熟练的选择和使用各个模式要了解每一个模式的应用场景。比方这次学习的策略模式。在大话设计... 查看详情
设计模式笔记-策略模式(代码片段)
设计模式笔记-策略模式文章目录设计模式笔记-策略模式1.策略模式2.常见写法3.常见框架中的策略模式1.策略模式策略模式(Strategy),定义了一组算法,将每个算法都封装起来,并且使它们之间可以互换,在使... 查看详情