在说策略模式之前,我们先来分析一个需求。
某商场经常有促销活动,第一次是满200送80,第二次是所有商品打8折,第三次是所有商品打9折,第四次是满300送120等等。如果让你设计你怎么做?
我们都在说面向对象编程,但是并不是类越多越好。分类的目的是为了抽象,将所有共性(相同的属性和功能)的对象抽象成一个类。
我们先来找共性,打折类的需求我们可以抽象成一个需求,因为他们的共性就是折扣率和原价。
满多少送多少类可以抽象成一个需求,因为他们的共性就是总价和扣除价。
正常价是原价。
从需求中我们可以看到,我们现在有三类需求,这三类需求分别抽象成三个共性类:正常价格类,折扣类,满多少送多少类。
我们想一下,这三个类有什么相同点?就是不管是哪个类,既然是卖商品,最后都要算总价,因此,我们可以定义一个父类cashSuper,提供统一方法接口cashTotal。因此,我们抽象出的结构类图如下:
?
我们按照上述类图,来定义各类:
父类CashSuper,提供总价接口。?
class="父类CashSuper ">public interface CashSuper { double cashTotal(double total); }
?正常价类,实现父类接口,总价计算方式为商品原总价。
public class CashNomal implements CashSuper { public double cashTotal(double total) { return total; } }
?折扣类,实现父类接口,总价计算方式为 折扣*商品原总价:
public class CashDiscount implements CashSuper { private double discount; public CashDiscount(String discount){ this.discount = Double.parseDouble(discount); } public double cashTotal(double total) { //最后的商品总价是 原总价*折扣 return total*discount; } }
?返利类,实现父类接口,总价计算方式为 商品原总价-根据返利条件算出的返利金额:
public class CashReturn implements CashSuper { //返利条件 private double returnCondition; //返利金额 private double returnMoney; //对于消费满多少送多少的,我们需要知道返利的条件(比如满300),我们还需要满足条件后赠费的金额 public CashReturn(String returnCondition,String returnMoney){ this.returnCondition = Double.parseDouble(returnCondition); this.returnMoney = Double.parseDouble(returnMoney); } public double cashTotal(double total) { //最后的商品总价是 总价减去返利价 if(total>this.returnCondition){ total = total - Math.floor(total/returnCondition)*returnMoney; return total; } return total; } }
?
好了,根据UML类图我们建立了基本的类,并且都实现了CashSuper中的总价计算方法。这么多类,每个类的的总价计算方法不尽相同,我们下一步怎么做?
我们看看类图,CashSuper是所有实现的父接口类,让一个对象持有这个接口类的实现对象,我们是不是可以不需要关注具体实现呢?我们尝试下。先修改类图。
?定义一个CashContext类,它持有CashSuper类,在获取总价时,让他负责去找对应的实现,我们不关心实现内容,具体如下:
public class CashContext{ //持有各种促销手段的父类对象 private CashSuper cs; //根据具体的促销策略,我们初始化父类实现 public CashContext(CashSuper cs){ this.cs = cs; } //直接将原总价传递,让具体的实现去处理得到最后的总价 public double getTotal(double total) { return cs.cashTotal(total); } }
?但是这么做,客户端就需要承担初始化CashContext的任务,如下:
public static void main(String[] args){ int strategyType = 1; double oldTotal = 888; CashContext cc = null; //1.代表正常收费 2.代表打八折 3.代表满300送100 switch (strategyType){ case 1: cc = new CashContext(new CashNomal()); break; case 2: cc = new CashContext(new CashDiscount("0.8")); break; case 3: cc = new CashContext(new CashReturn("300","100")); break; } System.out.println("最后的总价是:"+cc.getTotal(oldTotal)); }
那么有没有更好的方式,将这些判断逻辑转移,而使客户端对外统一?我们看到,上面的判断代码主要是为了获取不同的CashSuper,那么我们就可以把生产CashSuper过程使用简单工厂模式去做。我们先修改CashContext类,让它自己去根据类型去获取CashSuper,代码如下:
public class CashContext{ //持有各种促销手段的父类对象 private CashSuper cs; //根据具体的促销策略,我们初始化父类实现 public CashContext(int strategyType){ //1.代表正常收费 2.代表打八折 3.代表满300送100 switch (strategyType){ case 1: cs = new CashNomal(); break; case 2: cs = new CashDiscount("0.8"); break; case 3: cs = new CashReturn("300","100"); break; } this.cs = cs; } //直接将原总价传递,让具体的实现去处理得到最后的总价 public double getTotal(double total) { return cs.cashTotal(total); } }
?这样,我们的客户端代码就很简单了,只需要传入促销策略类型,其他的事情就不需要管了:
public static void main(String[] args){ //1.代表正常收费 2.代表打八折 3.代表满300送100 int strategyType = 3; double oldTotal = 888; CashContext cc = new CashContext(strategyType); System.out.println("最后的总价是:"+cc.getTotal(oldTotal)); }
?
二、总结
我们回过头来,什么是策略模式。策略模式定义了一组算法,分别封装起来,让它们之间可以互相替换,而算法的变化不会影响客户的使用。每一种算法的要完成的工作是相同的,只是实现不同,因此我们可以抽象出公共的算法接口类Strategy。那么为什么要增加Context类?为了功能层次清晰,因为Context类拥有了Strategy类的实现对象,只要提供了策略类Strategy的具体实现对象,Context类就可以统一对外提供客户端想要的结果。这样,外部看起来是透明统一的。这样,后期维护时候,不需要修改客户端代码,只需要修改统一对外的Context类。也就是说,策略模式对外的统一对象就是Context。
?
我们看下策略模式的结构类图:
?
当然,让Context类中承担判断也是让人很不爽的,一堆判断,后期增加促销类型时,虽然客户端不用管,但是也得修改这里,所以,我们在说到反射时,再优化这里的代码。
?
?
?
?
?
?