zoukankan      html  css  js  c++  java
  • 策略模式——(+简单工厂模式+反射)

    策略模式,需要我们结合简单工厂模式,更高级地用法可能需要我们掌握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,在上面两篇博文中已经有提到过,这里不再叙述。其实在客户端测试代码中,我们还可以进一步把代码写得再优美一点。

  • 相关阅读:
    一代人的青春--芳华
    用切面对监控日志的实现2
    一个在java后台实现的对图片进行加网纹或水印的工具类
    家乡的河
    家乡的鬼节—十来一儿
    八里沟印象
    双城记
    记忆中的那一树梨花
    用切面对监控日志的实现
    关于poi导出excel三种方式HSSFWorkbook,SXSSFWorkbook,csv的总结
  • 原文地址:https://www.cnblogs.com/yulinfeng/p/5891161.html
Copyright © 2011-2022 走看看