设计模式--策略模式
上篇聊完了简单工厂模式,这篇来说说策略模式。那么还是以一个例子来进行说明。
某公司中要开发一个商场收银的软件,由小明进行开发。以下是他的代码详情:
java
public class Test {
public static void main(String[] args) {
double price; // 商品单价
int num; // 商品数量
double totalPrice; // 当前商品合计费用
double total = 0d; // 总计商品合计费用
Scanner sc = new Scanner(System.in);
do {
System.out.println("输入商品的销售模式:1.原价 2.八折 3.七折");
int discount = Integer.parseInt(sc.nextLine());
System.out.println("输入商品单价:");
price = Double.parseDouble(sc.nextLine());
System.out.println("输入商品数量:");
num = Integer.parseInt(sc.nextLine());
System.out.println();
if (price > 0 && num > 0) {
// 计算当前金额和总金额
totalPrice = price * num;
total = total + totalPrice;
System.out.println("单价:" + price + "元 数量:" + num + " 合计:" + totalPrice + "元");
System.out.println("总计:" + total " 元");
}
} while (price > 0 && num > 0);
}
}
完事儿后提交给Leader,Leader看完后摇了摇头,你这个确实能实现我要求的功能,但是当我需要做活动,打折时怎么办呢?
小明一听,这不简单,我根据商品的销售模式增加代码就行了。
java
int discount = Integer.parseInt(sc.nextLine()); // 接收到商品销售模式的参数
// 计算当前金额和总金额
switch (discount) {
case 1:
totalPrice = price * num;
break;
case 2:
totalPrice = price * num * 0.8;
break;
case 3:
totalPrice = price * num * 0.7;
break;
}
total = total + totalPrice;
Leader看完后还是摇了摇头,说:"这比刚才确实灵活性上好多了,不过重复的代码很多啊,比如这3个case分支要执行的语句除了打折多少以外几乎没有什么不同的了,应该考虑重构一下了。不过这还不是主要的,现在我又有新的需求:需要满300返100的促销算法,怎么办呢?"
小明想了想,突然灵光一现,好像可以使用简单工厂模式啊,加了功能又不影响全局的代码。说干就干
使用简单工厂
又建了几个类:
- 收费的抽象类
java
public abstract class CashSuper {
// 接收价格和数量参数
public abstract double acceptCash(double price, int num);
}
- 正常收费类
java
public class CashNormal extends CashSuper {
@Override
public double acceptCash(double price, int num) {
// 原价
return price * num;
}
}
- 打折收费类
java
public class CashRebate extends CashSuper {
private double moneyRebate = 1d;
// 初始化时必须输入打折的折扣,八折就是0.8
public CashRebate(double moneyRebate){
this.moneyRebate = moneyRebate;
}
@Override
public double acceptCash(double price, int num) {
// 打折
return price * num * moneyRebate;
}
}
- 返现类
java
public class CashReturn extends CashSuper {
private double moneyCondition = 0d; // 返利条件
private double moneyReturn = 0d; // 返回金额
// 返利收费,初始化时必须输入返利条件和返利值
// 如300返100,moneyCondition = 300 , moneyReturn = 100
public CashReturn(double moneyCondition, double moneyReturn) {
this.moneyCondition = moneyCondition;
this.moneyReturn = moneyReturn;
}
@Override
public double acceptCash(double price, int num) {
// 满足条件时,原价 - 返现值
double result = price * num;
if (moneyCondition > 0 && result >= moneyCondition) {
result = result - Math.floor(result / moneyCondition) * moneyReturn;
}
return result;
}
}
- 收费工厂(确认收费模式)
java
public class CashFactory {
public static CashSuper createCashAccept(int cashType) {
CashSuper cs = null;
switch (cashType) {
case 1:
cs = new CashNormal();//正常收费
break;
case 2:
cs = new CashRebate(0.8d); // 八折
break;
case 3:
cs = new CashRebate(0.7d); // 七折
break;
case 4:
cs = new CashReturn(300d, 100d); // 满300返100
break;
}
return cs;
}
}
客户端调用代码:
java
public class Test {
public static void main(String[] args) {
double price; // 商品单价
int num; // 商品数量
double totalPrice = 0d; // 当前商品合计费用
double total = 0d; // 总计商品合计费用
Scanner sc = new Scanner(System.in);
do {
System.out.println("输入商品的销售模式:1.原价 2.八折 3.七折");
int discount = Integer.parseInt(sc.nextLine());
System.out.println("输入商品单价:");
price = Double.parseDouble(sc.nextLine());
System.out.println("输入商品数量:");
num = Integer.parseInt(sc.nextLine());
CashSuper cashAccept = CashFactory.createCashAccept(discount);
totalPrice = cashAccept.acceptCash(price, num);
total += totalPrice;
} while (price > 0 && num > 0);
}
}
搞定了,这次无论怎么改,都可以进行简单处理,就算增加新的活动,直接创建新的活动类实现自己的业务算法就行了。
Leader看后点了点头,重构了一波代码确实看上去好多了。随即又问:"假如我现在新增了一个促销手段,积分积累满100积分10点,积累到一定积分可以兑换礼品。虽然你可以用这个简单工厂模式,但是这个模式只是能解决对象的创建问题,而且由于工厂本身包括所有的收费方式,商场是可能经常性更改打折额度和返利额度,每次维护和扩展收费方式,你都需要改动这个对象工厂,以导致代码需要重新编译部署,真的是很糟糕的处理方式,所以这个不是最好的方法。面对算法的时长变动,应该会有更好的办法的。"
策略模式
次日小明找到了合适的模式--策略模式:它定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的客户。
商场收银时如何促销,用打折还是返利,其实都是一些算法,用工厂来生成算法对象,这没有问题,但是算法本身只是一种策略,最重要的是这些算法是随时可能互相替换的,这就是变化点,而封装变化点则是我们面向对象的一种很重要的思维方式。
策略模式概要
看下策略模式架构
大致理解
- Strategy类,定义所有支持算法的公共接口
java
public abstract class Strategy {
// 算法方法
public abstract void algorithmInterface();
}
- ConcreteStrategy类,封装具体的算法和行为,继承Strategy
java
public class ConcreteStrategyA extends Strategy {
@Override
public void algorithmInterface() {
System.out.println("算法A实现");
}
}
public class ConcreteStrategyB extends Strategy {
@Override
public void algorithmInterface() {
System.out.println("算法B实现");
}
}
public class ConcreteStrategyC extends Strategy {
@Override
public void algorithmInterface() {
System.out.println("算法C实现");
}
}
- Context上下文,用一个ConcreteStrategy来配置,维护一个对Strategy对象的引用
java
public class Context {
private Strategy strategy;
// 初始化时,传入具体的策略对象
public Context(Strategy strategy) {
this.strategy = strategy;
}
// 上下文接口
public void contextInterface() {
// 根据具体的策略对象,调用其算法的方法
strategy.algorithmInterface();
}
}
客户端代码:
java
Context context;
context = new Context(new ConcreteStrategyA());
context.contextInterface();
context = new Context(new ConcreteStrategyB());
context.contextInterface();
context = new Context(new ConcreteStrategyC());
context.contextInterface();
实例化不同的策略对象,调用不同的策略算法。那么实际中如何使用呢?
实现策略模式
实际上,前面案例中所写的几个类就是策略的算法类了,CashSuper类是抽象策略,正常收费CashNormal、打折收费CashRebate和返利收费CashReturn就是三个具体策略,也就是策略模式中说的具体算法。
那么现在只需要增加一个Context的类就行了。
java
public class CashContext {
private CashSuper cs;
public CashContext(CashSuper cs) {
this.cs = cs;
}
public double getResult(double price, int num) {
return cs.acceptCash(price, num);
}
}
客户端主要代码:
java
CashContext cc = null;
switch (cashType) {
case 1:
cc = new CashContext(new CashNormal());//正常收费
break;
case 2:
cc = new CashContext(new CashRebate(0.8d)); // 八折
break;
case 3:
cc = new CashContext(new CashRebate(0.7d)); // 七折
break;
case 4:
cc = new CashContext(new CashReturn(300d, 100d)); // 满300返100
break;
}
totalPrice = cc.getResult(price, num)l;
total += totalPrice;
写出来后,发现其实好像和工厂模式没什么两样啊,Leader又问道:"你有没有办法将这个判断从客户端这里转移走呢,让工厂来帮你判断你需要生成的算法对象呢?难道简单工厂模式必须是一个单独的类吗,难道不可以和策略模式一起用吗?"
策略与简单工厂结合
小明恍然大悟,原来还可以这样做,马上开始改造代码:
java
public class CashContext {
private CashSuper cs;
// 通过构造方法传入收费策略
public CashContext(int cashType) {
CashSuper cs = null;
switch (cashType) {
case 1:
this.cs = new CashNormal();//正常收费
break;
case 2:
this.cs = new CashRebate(0.8d); // 八折
break;
case 3:
this.cs = new CashRebate(0.7d); // 七折
break;
case 4:
this.cs = new CashReturn(300d, 100d); // 满300返100
break;
}
}
public CashContext(CashSuper cs) {
this.cs = cs;
}
public double getResult(double price, int num) {
return cs.acceptCash(price, num);
}
}
客户端调用时
java
CashContext cc = new CashContext(discount);
totalPrice = cc.getResult(price, num);
total += totalPrice;
Leader看后很满意,说道:"这样子不就对了,简单工厂模式并非只有建一个工厂类的做法,和策略模式一起用起来,不就省了更多事儿了吗。你刚开始的简单工厂也没问题,能解决具体需要哪个算法对象的问题,但是客户端调用时,他是不是必须要认识你的CashSuper啊和CashFactory类呢。但是现在你把策略模式和简单工厂一结合。客户端调用时只需要认识你的CashContext类,其他什么都不用管。耦合度是不是更低了。"
总结
策略模式是一种定义一系列算法的方法,从概念上看,所有这些算法都是完成相同的工作,只是实现不同,它可以以相同的方式调用所有的算法,减少了各种算法类和使用算法类之间的耦合度。
那么除此之外,策略模式能简化单元测试,每个算法都有自己的类,测试时只需要实例化这个算法类给上参数,就能直接测试,修改其中任意一个算法类也不会对其他算法类有影响。
在基本的策略模式中,选择所用具体实现的职责由客户端对象承担,并且转给策略模式的Context对象。这本身没有解除客户端需要选择判断的压力,而策略模式和简单工厂模式结合后,选择具体实现的职责也由Context来承担,这就最大化减轻了客户端的职责。