书中通过一道题目,让"小菜"设计一个商场收银软件,营业员根据客户购买商品的数量及单价,想客户收费。,并且软件可以兼容打折,返点等优惠活动的金额计算。
一开始,小菜使用之前所学的简单工厂模式来制作这个收银软件。
收费父类
public abstract class CashSuper {
/**
* 收费方法
* @param money //原价
* @return
*/
public abstract double acceptCash(double money);
}
普通收费子类
/**
* 普通计算类
*
*/
public class CashNormal extends CashSuper{
@Override
public double acceptCash(double money) {
return money;
}
}
打折收费类
//打折类
public class CashRebate extends CashSuper{
private double cashRebate = 1d;//折扣
public CashRebate(double cashRebate) {
super();
this.cashRebate = cashRebate;
}
@Override
public double acceptCash(double money) {
return money*cashRebate;
}
}
满减收费类
public class CashReturn extends CashSuper{
private double moneyCondition = 0.0d;//满多少钱开始反
private double moneyReturn = 0.0d;//反多少钱
public CashReturn(double moneyCondition, double moneyReturn) {
super();
this.moneyCondition = moneyCondition;
this.moneyReturn = moneyReturn;
}
@Override
public double acceptCash(double money) {
return money>moneyCondition?
money-Math.floor(money/moneyCondition)*moneyReturn:money;
}
}
收费工厂类
//收费工厂
public class CashFactory {
public static CashSuper createCashSuper(String type){
CashSuper cs = null;
switch (type) {
case "打8折":
cs=new CashRebate(0.8);
break;
case "满300减100":
cs=new CashReturn(300,100);
break;
default:
cs=new CashNormal();
break;
}
return cs;
}
}
主方法
public class Main {
public static void main(String[] args) {
double totalPrice = 0.0d;//总金额
String type = "满300减100";
CashSuper cs =CashFactory.createCashSuper(type);
double price = 400d;//未打折前金额
totalPrice+=cs.acceptCash(price);//打折后金额
System.out.println(totalPrice);
}
}
但是这里就遇到了我们在学习简单工厂方法时想到的问题,按照大鸟的话来讲;“简单工厂模式虽然也能解决这个问题,但是简单工厂模式只是解决对象的创建问题。而且由于工厂本身包含所有的收费方式,商场可能经常性的更改打折和满减方式,每次修改和扩展收费方式都要修改工厂类,以至于代码需要重新编译部署。这真的是很糟糕的处理方式,不是最好的方法。”
于是,引出了策略模式。。。。
策略模式(Strategy):它定义了算法家族,分别封装起来,让他们之间可以互相替代,此模式让算法的变化,不用影响使用算法的客户。
策略模式结构图
通过使用策略模式,修改代码,将打折,满减等算法,都有Context来配置,于是只需要加一个Context类和改一下主方法。
public class CashContext {
private CashSuper cashSuper;//收费接口
public CashContext(CashSuper cashSuper) {
this.cashSuper = cashSuper;
}
public double getResult(double money){
return cashSuper.acceptCash(money);
}
}
主方法:
public class Main {
public static void main(String[] args) {
double totalPrice =0.0d;//用于小计
CashContext cc=null;
String type = "满300减100";
switch (type) {
case "满300减100":
cc=new CashContext(new CashReturn(300, 100));
break;
case "打9折":
cc=new CashContext(new CashRebate(0.9));
break;
default:
cc=new CashContext(new CashNormal());
break;
}
totalPrice+=cc.getResult(400);
System.out.println(totalPrice);
}
}
但是这样又需要客户端去判断到底执行什么算法。于是引出了策略模式与简单工厂模式的结合。
于是改造CashContext和主方法
public class CashContext {
private CashSuper cashSuper;//收费接口
public CashContext(String type) {
super();
switch (type) {
case "满300减100":
cashSuper=new CashReturn(300, 100);
break;
case "打9折":
cashSuper=new CashRebate(0.9);
break;
default:
cashSuper=new CashNormal();
break;
}
}
public double getResult(double money){
return cashSuper.acceptCash(money);
}
}
主方法:
public class Main {
public static void main(String[] args) {
double totalPrice =0.0d;//用于小计
CashContext cc=null;
String type = "满300减100";
cc = new CashContext(type);
totalPrice+=cc.getResult(400);
System.out.println(totalPrice);
}
}
对比简单工厂模式和策略模式与工厂模式的结合, 简单工厂模式需要客户端认识两个类CashSuper和CashFactory,而策略模式和工厂模式的结合,客户端只需要认识Context一个类就可以了,代码的耦合性进一步降低。
策略模式解析:
策略模式优点:1、它可以以相同的方式来调用所有的方法,减少了各种算法类之间的耦合。例如上面例子中,无论是打折,还是满减,客户端调用的都是Context.getResult();。
2、简化了单元测试,每个算法都有自己的类,可以通过自己的接口进行单元测试。
3、当一个算法出现问题是,对其他算法没有影响。
策略模式应用场景:
只要在分析过程中听到有不同时间应用不同的业务规则,就可以考虑应用策略模式来处理这种变化。
策略模式的重心
策略模式的重心不是如何实现算法,而是如何组织、调用这些算法,从而让程序结构更灵活,具有更好的维护性和扩展性。
策略模式的缺点
(1)客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。换言之,策略模式只适用于客户端知道算法或行为的情况。
(2)由于策略模式把每个具体的策略实现都单独封装成为类,如果备选的策略很多的话,那么对象的数目就会很可观。