java 反射 工厂模式_策略模式——(+简单工厂模式+反射)

策略模式,需要我们结合简单工厂模式,更高级地用法可能需要我们掌握Java反射机制。简单工厂模式我们在最早的时候介绍,我们也谈到了一点Java的反射机制。借着学习策略模式的机会,我们顺便复习一下简单工厂模式和反射。

先说说何为策略模式。“策略”我的理解是,对一件事,有不同的方法去做,至于用何种方法取决于我们的选择。我们同样借助《大话设计模式》中实现策略模式的例子来做讲解。

超市进场做活动,我们现在假设有正常不减价、打折、满减这三种活动,这正是对“买东西收费”这件事,有三种不同的“方法”,这三种方法其实就是三种不同的算法。我们定义出策略模式:它定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化不会影响到使用算法的客户。看到这个可能还是一脸茫然,不着急我们一步一步来这句话到底想表达什么意思。

首先,对于正常不减价,我们可以直接计算返回该收取的金额为多少。对于打折的这种情况,我们可能会想到传递一个“打多少折”的参数进去,计算返回该收取的金额为多少。对于满减的这种情况,我们传递两个参数,“返利条件”及“返多少利”,计算返回该收取的金额为多少。那么它们似乎都有一个公共方法,对于应收金额,返回实收金额。我们可以将三种情况抽取为一个接口或抽象类。来试着画出UML类图结构。

b4c19092a4b50a2a59e43e900b201791.png

看到UML的类结构图,我们其实可以联想到简单工厂模式,如果我们就这样来写,在客户端就需要来具体实例化哪一个类。我们不想在客户端来做出判断决定来实例化哪一个类,这个时候怎么办呢——简单工厂模式可以帮我们实现。客户端不决定具体实例化哪一个类,而是交由“工厂”来帮我们实例化。所以其实我们首先是实现的一个“简单工厂模式”。

所以我们上面的UML类结构图就可以做下修改。

a9b860a33dd88c8b116b4c0c3b51e6c8.png

接下来写出我们的代码。

1 packageday_20_cash;2

3 /**

4 * 收费接口5 *@authorturbo6 *7 * 2016年9月20日8 */

9 public interfaceCashSuper {10 /**

11 * 计算实收的费用12 *@parammoney 应收金额13 *@return实收金额14 */

15 double acceptCash(doublemoney);16 }

1 packageday_20_cash;2

3 /**

4 * 正常收费5 *@authorturbo6 *7 * 2016年9月20日8 */

9 public class CashNormal implementsCashSuper {10

11 /*(non-Javadoc)12 * @see day_20_cash.CashSuper#acceptCash(double)13 */

14 @Override15 public double acceptCash(doublemoney) {16

17 returnmoney;18 }19

20 }

1 packageday_20_cash;2

3 /**

4 * 打折5 *@authorturbo6 *7 * 2016年9月20日8 */

9 public class CashRebate implementsCashSuper {10 private doublemoneyRebate;11

12

13 /**

14 *@parammoneyRebate 折扣率15 */

16 public CashRebate(doublemoneyRebate) {17 this.moneyRebate =moneyRebate;18 }19

20

21 /*(non-Javadoc)22 * @see day_20_cash.CashSuper#acceptCash(double)23 */

24 @Override25 public double acceptCash(doublemoney) {26

27 return money * (moneyRebate / 10);28 }29

30 }

1 packageday_20_cash;2

3 /**

4 * 满减5 *@authorturbo6 *7 * 2016年9月20日8 */

9 public class CashReturn implementsCashSuper {10 private double moneyCondition; //应收金额

11 private double moneyReturn; //返利金额

12

13 public CashReturn(double moneyCondition, doublemoneyReturn){14 this.moneyCondition =moneyCondition;15 this.moneyReturn =moneyReturn;16 }17 /*(non-Javadoc)18 * @see day_20_cash.CashSuper#acceptCash(double)19 */

20 @Override21 public double acceptCash(doublemoney) {22 if (money >=moneyCondition){23 money = money -moneyReturn;24 }25 returnmoney;26 }27

28 }

1 packageday_20_cash;2

3 /**

4 * 收费对象生成工厂5 *@authorturbo6 *7 * 2016年9月20日8 */

9 public classCashFactory {10 public staticCashSuper createCashAccept(String cashType){11 CashSuper cs = null;12 switch(cashType) {13 case "正常收费":14 cs = newCashNormal();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 returncs;27 }28 }

1 packageday_20_cash;2

3 /**

4 * 客户端抽象代码5 *@authorturbo6 *7 * 2016年9月20日8 */

9 public classMain {10

11 /**

12 *@paramargs13 */

14 public static voidmain(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 packageday_20_cash;2

3 /**

4 * Context上下文,维护对strategy对象的引用5 *@authorturbo6 *7 * 2016年9月21日8 */

9 public classCashContext {10 CashSuper cs = null;11 publicCashContext(CashSuper csuper){12 this.cs =csuper;13 }14

15 public double getResult(doublemoney){16

17 returncs.acceptCash(money);18 }19 }

再来看客户端代码怎么写。

1 packageday_20_cash;2

3 /**

4 * 客户端抽象代码5 *@authorturbo6 *7 * 2016年9月20日8 */

9 public classMain {10

11 /**

12 *@paramargs13 */

14 public static voidmain(String[] args) {15 CashContext context = null;16 double money = 0.0;17 String strategy = "打8折";18 switch(strategy) {19 case "正常收费":20 context = new CashContext(newCashNormal());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 packageday_20_cash;2

3 /**

4 * Context上下文,维护对strategy对象的引用5 *@authorturbo6 *7 * 2016年9月21日8 */

9 public classCashContext {10 CashSuper cs = null;11 publicCashContext(String type){12 switch(type) {13 case "正常收费":14 CashNormal normal = newCashNormal();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(doublemoney){29

30 returncs.acceptCash(money);31 }32 }

客户端测试代码:

1 packageday_20_cash;2

3 /**

4 * 客户端抽象代码5 *@authorturbo6 *7 * 2016年9月21日8 */

9 public classMain {10

11 /**

12 *@paramargs13 */

14 public static voidmain(String[] args) {15 CashContext context = null;16 double money = 0.0;17 String strategy = "打8折";18 context = newCashContext(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 packageday_20_cash;2

3 importjava.lang.reflect.Constructor;4 importjava.lang.reflect.InvocationTargetException;5

6 /**

7 * Context上下文,维护对strategy对象的引用8 *@authorturbo9 *10 * 2016年9月22日11 */

12 public classCashContext {13 Class> clazz = null;14 Object obj = null;15 publicCashContext(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(doublemoney){37

38 return((CashSuper)obj).acceptCash(money);39 }40 }

修改客户端测试代码:

1 packageday_20_cash;2

3 /**

4 * 客户端测试代码5 *@authorturbo6 *7 * 2016年9月22日8 */

9 public classMain {10

11 /**

12 *@paramargs13 */

14 public static voidmain(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 = newCashContext(type, paramTypes, params);21 money = context.getResult(300);22 System.out.println(money);23 }24

25 }

至于为什么要用到反射来消除switch,在上面两篇博文中已经有提到过,这里不再叙述。其实在客户端测试代码中,我们还可以进一步把代码写得再优美一点。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值