前言
本篇实现一个模拟收银的小程序, 来了解什么是策略模式, 和策略模式的好处都有哪些, 另外, 不像上一篇博文, 一点一点衍化了, 省略特别简单的版本吧, 直接过渡到有一点封装的版本, 因为能看设计模式的应该都不是刚入行的新手, 有些东西一句话带过就可以了.
正文
场景:
一个简单的收银系统
界面如图
输入价格
和单价
, 根据所选的不同计费方式
计算总价的功能, 界面就是为了直观化功能, 其实你在练习时完全可以用控制台模拟输入, 代替此界面, java画这样的界面很不友好.
代码
假设计费方式分为 "正常", "打8折", "满300-100", 分别用0, 1, 2表示, 可以写出如下方法:
// 单价, 数量, 计费方式
public static String operating(double price, double number, int type) {
double total = 0;
switch (type) {
case 0:
total = price * number;
break;
case 1:
total = price * number * 0.8;
break;
case 2:
total = price - Math.floor((price/300)) * 100;
break;
default:
break;
}
return String.valueOf(total);
}
尽量不要把分支留给客户端处理, 学习了简单工厂模式, 我们可以改造一下, 先像简单工厂模式过度.
首先明确工厂基类, 三者存在共性, 就是都处理同一个价格, 即商品的原价格. 然后分析这几中收费方式, 打折收费, 不管打8折, 打7折, 打几折应该都是打折的算法吧, 所以打折的类算一个实例, 正常的收费算一个实例, 满减活动算一个实例, 这里, 不可能会有人把打1~10折做10个类的对吧?
面向对象的编程, 并不是类越多越好, 类的划分是为了封装, 但分类的基础是抽象, 具有相同属性和功能的对象的抽象集合才是类
父类和子类:
// 处理价格超类
public abstract class CashPaySuper {
// 处理价格 参数:原价格
public abstract double calCash(double price);
}
// 正常收费类
public class CashPayNormal extends CashPaySuper {
@Override
public double calCash(double price) {
return price;
}
}
// 打折收费类
public class CashPayDiscount extends CashPaySuper {
// 折扣率
private double bate = 1;
// 初始化时可传入折扣率
public CashPayDiscount(double bate) {
this.bate = bate;
}
@Override
public double calCash(double price) {
return price * bate;
}
}
// 满减收费类
public class CashPayFullSub extends CashPaySuper {
// 满多少
double fullMoney = 0;
// 减多少
double subMoney = 0;
public CashPayFullSub(double fullMoney, double subMoney) {
this.fullMoney = fullMoney;
this.subMoney = subMoney;
}
@Override
public double calCash(double price) {
if (price >= fullMoney) {
price = price - Math.floor((price/fullMoney)) * subMoney;
}
return price;
}
}
简单工厂类:
public class CashPayFactrory {
public CashPaySuper createCashPay(int strategy) {
CashPaySuper cs = null;
switch (strategy) {
case 0:
// 正常销售
cs = new CashPayNormal();
return cs;
case 1:
// 打折销售(传入折扣率)
cs = new CashPayDiscount(0.8);
return cs;
case 2:
// 满减销售(传入满多少,减多少)
cs = new CashPayFullSub(300, 100);
return cs;
default:
break;
}
return null;
}
}
客户端的执行代码:
public static String operating(double price, double number, int selectIndex) {
CashPayFactrory cpf = new CashPayFactrory();
CashPaySuper cs = cpf.createCashPay(selectIndex);
return String.valueOf(cs.calCash(price * number));
}
简单工厂模式虽然解决了一定的问题,但这个模式只是解决对象的创建问题, 而且由于工厂本身包括了所有的收费方式, 商场可能要经常性的变更打折额度等,每次维护或扩展收费方式都要改动这个工厂, 以至代码需要重新编译部署, 所以它不是最好的办法, 面对算法的时常变动, 应该有更好的办法.
策略模式
不管商场如何促销,其实都是一些算法.用工厂来生成算法对象没有错, 但算法本身只是一种策略, 最重要的是这些算法随时都可能互相替换, 这就是变化点, 而封装变化点是我们面向对象的一种很重要的思维方式.
策略模式的基本结构图
我将上述例子重写设计一下:
UML图部分名字我改了, 用来和上面的策略模式学习, 不用在意
CashPaySuper就是抽象策略类, CashPayNormal, CashPayFullSub, CashPayDiscount类就是具体的策略, 所以这四个类不用改动.
只要加一个CashContext类, 再把客户端的执行代码稍微改动一下
CashContext类:
public class CashContext {
CashPaySuper cashPay;
public CashContext(CashPaySuper cashPay) {
this.cashPay = cashPay;
}
public double Cal(double price) {
return cashPay.calCash(price);
}
}
执行代码:
public static String operating(double price, double number, int type) {
CashContext context = null;
switch (type) {
case 0:
context = new CashContext(new CashPayNormal());
break;
case 1:
context = new CashContext(new CashPayDiscount(0.8));
break;
case 2:
context = new CashContext(new CashPayFullSub(300, 100));
break;
default:
break;
}
return String.valueOf(context.Cal(price*number));
}
执行代码和之前的比, 好像又回到了第一版的样子, 又在客户端处理分支了, 所以我们需要把分支想办法挪走.
策略模式与简单工厂模式结合
改造CashContext类:
public class CashContext {
CashPaySuper cashPay = null;
public CashContext(int type) {
switch (type) {
case 0:
this.cashPay = new CashPayNormal();
break;
case 1:
this.cashPay = new CashPayDiscount(0.8);
break;
case 2:
this.cashPay = new CashPayFullSub(300, 100);
break;
default:
break;
}
}
public double Cal(double price) {
return cashPay.calCash(price);
}
}
最后改造执行代码:
public static String operating(double price, double number, int type) {
return String.valueOf(new CashContext(type).Cal(price*number));
}
对比之前的简单工厂的代码:
public static String operating(double price, double number, int selectIndex) {
CashPayFactrory cpf = new CashPayFactrory();
CashPaySuper cs = cpf.createCashPay(selectIndex);
return String.valueOf(cs.calCash(price * number));
}
显然代码更精简了, 暴露给客户端的东西更少了, 仅一个CashContext对象而已, 耦合度降低了.
策略模式解析
策略模式是一种定义一系列算法的方法, 从概念上来看, 所有这些算法的完成都是相同的工作, 只是实现不同, 它可以以相同的方式调用所有的算法, 减少了各种算法类与使用算法之间的耦合.
关于策略模式的总结
- 策略模式简化了单元测试, 每个算法都有自己的类, 可以通过自己的接口单独测试
- 修改其中一个算法, 不会影响其他算法
- 当不同的行为堆砌到一个类中, 难免有switch , if等分支, 将这些行为封装在一个个独立的Strategy类中, 可以在使用这些行为的类中消除条件语句
- 策略模式几乎可以封装任何类型的规则, 只要在分析过程中听到需要在不同的时间应用不同的业务规则, 就可以考虑使用策略模式处理这种变化的可能性
- 基本的策略模式, 选择所用具体实现的职责由客户端承担, 并转给策略模式的Context对象.但与工厂模式结合后, 选择的职责也由Context承担, 大大减轻了客户端的职责.
参考书籍
- 大话设计模式