首页 > 代码库 > 策略模式

策略模式

策略模式,需要我们结合简单工厂模式,更高级地用法可能需要我们掌握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     @Override15     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     @Override25     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     @Override21     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 args13      */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 args13      */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“又移到”工厂中去呢?换句话说,策略模式和工厂模式相结合。

(未完)

 

策略模式