商场促销-策略模式
商场收银软件
大鸟给小菜出了一个作业,让小菜做一个商场收银软件,营业员根据客户端所购买商品的单价和数量,向用户收费。
- 核心代码如下:
import java.util.Scanner;
public class Main {
private static double total = 0;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入单价");
double prize = sc.nextDouble();
System.out.println("请输入数量(整数)");
int num = sc.nextInt();
total += num * prize;
System.out.println("单价:" + prize + "数量:" + num + "\n总价:" + total);
}
}
增加打折
-
现在大鸟要小菜增加打折功能,小菜回答道在
prize
乘以对倍率即可,大鸟又说道,如果现在活动过去了,不在打折了,你还要再次修改你的程序吗? -
小菜悟了:只需要加一个数组即可。每次计算的时候选择对应的倍率。
import java.util.Scanner;
public class Strategy02 {
private static final String[] rebate = new String[]{"正常收费","打八折","打七折","打五折"};
private static double total = 0;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入单价");
double prize = sc.nextDouble();
System.out.println("请输入数量(整数)");
int num = sc.nextInt();
System.out.println("请选择倍率");
for(int i = 0;i < rebate.length;i ++){
System.out.println(i + ": " + rebate[i]);
}
String k = sc.next();
switch (k){
case "0":
prize *= 1.0;
break;
case "1":
prize *= 0.8;
break;
case "2":
prize *= 0.7;
break;
case "3":
prize *= 0.5;
break;
}
total += num * prize;
System.out.println("单价:" + prize + "数量:" + num + "\n总价:" + total);
}
}
这次代码灵活性确实比上面的好多了。但是还有很多重复的代码,比如switch
这里仅仅是对倍率做了一个判断,如果我现在又推出了满300减100
的活动呢
这里可以使用之前的简单工厂设计模式,先写一个父类,再继承它实现多个打折和返利的子类,利用多态,完成代码。
简单工厂实现
面向对象编程,并不是类越多越好,类的划分只是为了封装,但分类的基础是抽象,具有相同属性和功能的对象的抽象集合才是类
- 现金收费抽象类
package Strategy03;
public abstract class CashSuper {
/**
* @param money 总价钱
* @return 优惠后的价钱
* */
public abstract double acceptCash(double money);
}
- 正常收费子类
package Strategy03;
public class CashNormal extends CashSuper{
@Override
public double acceptCash(double money) {
return money;
}
}
- 打折收费子类
package Strategy03;
public class CashRebate extends CashSuper{
private double cashRebate = 1.0;
public CashRebate() {
}
public CashRebate(double cashRebate) {
this.cashRebate = cashRebate;
}
@Override
public double acceptCash(double money) {
return money * this.cashRebate;
}
}
- 返利收费子类
package Strategy03;
public class CashReturn extends CashSuper{
private double moneyCondition = 0;
private double moneyReturn = 0;
public CashReturn() {
}
public CashReturn(double moneyCondition, double moneyReturn) {
this.moneyCondition = moneyCondition;
this.moneyReturn = moneyReturn;
}
@Override
public double acceptCash(double money) {
double res = money;
if(money >= moneyCondition){
res = money - Math.floor(money/moneyCondition) * moneyReturn;
}
return res;
}
}
- 现金收费工厂类
package Strategy03;
public class CashFactory {
public static CashSuper createCashAccept(String type){
CashSuper cashSuper = null;
switch (type){
case "正常收费":
cashSuper = new CashNormal();
break;
case "满300减100":
cashSuper = new CashReturn(300,100);
break;
case "打七折":
cashSuper = new CashRebate(0.7);
break;
}
return cashSuper;
}
}
- 客户端
package Strategy03;
import java.util.Scanner;
public class Client {
private static final String[] strategy = new String[]{"正常收费","满300减100","打七折"};
private static double total = 0;
public static void main(String[] args) {
System.out.println("请选择:");
for(int i = 0;i < strategy.length;i ++){
System.out.println(i + " : " + strategy[i]);
}
Scanner sc = new Scanner(System.in);
int idx = sc.nextInt();
CashSuper cashSuper = null;
try{
cashSuper = CashFactory.createCashAccept(strategy[idx]);
}catch (Exception e){
e.printStackTrace();
}
System.out.println("请输入单价");
double prize = sc.nextDouble();
System.out.println("请输入数量(整数)");
int num = sc.nextInt();
// 通过多态,获取不同的结果
total += cashSuper.acceptCash(prize * num);
System.out.println("单价:" + prize + " | 数量:" + num + "\n总价:" + total);
}
}
- 这次如果我需要打五折和满500减200,应该怎么办呢?
只需要在现金工厂加两个条件,客户端添加新的选项即可。
- 如果我还需要,满100积分10点,积分到一定程度可以兑奖如何做呢?
有了工厂,加一个积分算法,让他继承CashSuper,再到现金工厂新加条件分支即可。
简单工厂模式虽然也可以解决这个问题,但这个模式只能解决对象创建的问题,而且由于工厂本身包含了所有的收费方式,商场是经常更改打折额度和返利额度的,每次维护,扩展收费方式都要改动这个工厂,一直代码需要重新编译部署。
面对算法的时常改动,应该有更好的方法 – 策略模式
策略模式
策略模式(Strategy):它定义了算法家族,分别封装起来,让它们之间可以相互替换,此模式让算法的变化,不会影响到使用算法的客户。
- 银行收银时如何促销,打折还是返利,其实都是一些算法,用工厂来生成对象,这没有错,但算法本身只是一种策略,最重要的是这些算法是随时都可能相互替换的,这就是变化点,而封装变化点是我们面向对象的重要的思维方式。
- Strategy类,定义所有支持的算法的接口
package Strategy04;
// 抽象算法类
public abstract class Strategy {
// 算法方法
public abstract void AlgorithmInterface();
}
- ConcreteStrategy类,封装了具体的算法,继承Strategy类
package Strategy04;
// 具体算法A
public class ConcreteStrategyA extends Strategy{
// 算法A的实现
@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对象的引用
package Strategy04;
// 上下文,维护了Strategy对象的引用
public class Context {
Strategy strategy;
// 初始化时,传入具体的策略对象
public Context(Strategy strategy) {
this.strategy = strategy;
}
public void ContextInterface(){
// 根据具体策略对象,调用其算法
strategy.AlgorithmInterface();
}
}
- 客户端代码
package Strategy04;
public class Client {
public static void main(String[] args) {
Context context = null;
context = new Context(new ConcreteStrategyA());
context.ContextInterface();
context = new Context(new ConcreteStrategyB());
context.ContextInterface();
context = new Context(new ConcreteStrategyC());
context.ContextInterface();
// 由于实例化不同的策略,在调用ContextInterface()时,获得的结果也不同
}
}
// A策略的具体算法
// B策略的具体算法
// C策略的具体算法
策略模式实现
-
我们之前的CashSuper就是抽象类,而CashNormal,CashRebate,CashReturn就是决策模式中的具体算法。
-
只要增加一个CashContext即可。
-
CashContext类
package Strategy05;
public class CashContext {
CashSuper cashSuper;
public CashContext(CashSuper cashSuper) {
// 传入一个具体的策略
this.cashSuper = cashSuper;
}
public double handlerAcceptCash(double money){
return this.cashSuper.acceptCash(money);
}
}
- 客户端
package Strategy05;
import java.util.Scanner;
public class Client {
private static final String[] strategy = new String[]{"正常收费","满300减100","打七折"};
private static double total = 0;
public static void main(String[] args) {
System.out.println("请选择:");
for(int i = 0;i < strategy.length;i ++){
System.out.println(i + " : " + strategy[i]);
}
Scanner sc = new Scanner(System.in);
int idx = sc.nextInt();
CashContext context = null;
try{
switch (strategy[idx]){
case "正常收费":
context = new CashContext(new CashNormal());
break;
case "满300减100":
context = new CashContext(new CashReturn(300,100));
break;
case "打七折" :
context = new CashContext(new CashRebate(0.7));
break;
}
}catch (Exception e){
e.printStackTrace();
}
System.out.println("请输入单价");
double prize = sc.nextDouble();
System.out.println("请输入数量(整数)");
int num = sc.nextInt();
// 通过多态,获取不同的结果
total += context.handlerAcceptCash(prize * num);
System.out.println("单价:" + prize + " | 数量:" + num + "\n总价:" + total);
}
}
-
代码是模仿着写出来了,但是客户端判断CashContext类型的过程不又回到最开始的地方了吗?
-
如何把这个判断过程从客户端转移走呢?
-
简单工厂是可以转移的,但没有必要单独写一个类,如何与策略模式的Context类结合呢?
策略模式与简单工厂结合
- 改造后的CashContext类
package Strategy06;
public class CashContext {
CashSuper cashSuper;
public CashContext(String type) {
// 传入字符串,表示收费类型
try{
switch (type){
case "正常收费":
cashSuper = new CashNormal();
break;
case "满300减100":
cashSuper = new CashReturn(300,100);
break;
case "打七折" :
cashSuper = new CashRebate(0.7);
break;
}
}catch (Exception e){
e.printStackTrace();
}
}
public double handlerAcceptCash(double money){
return this.cashSuper.acceptCash(money);
}
}
- 客户端
package Strategy06;
import java.util.Scanner;
public class Client {
private static final String[] strategy = new String[]{"正常收费","满300减100","打七折"};
private static double total = 0;
public static void main(String[] args) {
System.out.println("请选择:");
for(int i = 0;i < strategy.length;i ++){
System.out.println(i + " : " + strategy[i]);
}
Scanner sc = new Scanner(System.in);
int idx = sc.nextInt();
CashContext context = new CashContext(strategy[idx]);
System.out.println("请输入单价");
double prize = sc.nextDouble();
System.out.println("请输入数量(整数)");
int num = sc.nextInt();
// 通过多态,获取不同的结果
total += context.handlerAcceptCash(prize * num);
System.out.println("单价:" + prize + " | 数量:" + num + "\n总价:" + total);
}
}
简单工厂模式与两种模式结合的不同
- 简单工厂
CashSuper cashSuper = null;
try{
cashSuper = CashFactory.createCashAccept(strategy[idx]);
}catch (Exception e){
e.printStackTrace();
}
// ...
total += cashSuper.acceptCash(prize * num);
- 简单工厂,策略模式相结合
CashContext context = new CashContext(strategy[idx]);
// ...
total += context.handlerAcceptCash(prize * num);
简单工厂需要客户端认识两个类(cashSuper,CashFactory)
简单工厂,策略模式相结合只需要让客户端认识CashContext一个类,耦合度更加降低。
调用
CashContext.handlerAcceptCash()
使得具体的算法彻底与客户端分离。就连具体算法的父类CashSuper也不让客户端知道。
策略模式解析
- 策略模式是一种定义一系列算法的方法,从概念上看,所有的这些算法都是在完成相同的工作,只是实现不同,它可以以相同的方式调用所有的算法,减少了各种算法类与使用算法类之间的耦合。
- 策略模式的
Strategy层
为Context定义了一系列的可供重用的算法或行为。继承有助于析取出这些算法中的公共功能。 - 简化了单元测试,因为每个算法都有自己的类,可以通过自己的接口单独测试。
- 当不同的行为堆砌的同一个类中,很难避免使用条件判断语句来选择合适的行为。将这些行为封装在一个个独立的Strategy类中,可以在使用这些行为时消除条件判断语句。
- 策略模式就是用来封装算法的,但在实践中,我们发现可以用它来封装几乎任何的类型规则,只要听到在不同的时间应用不同的业务规则,就可以考虑用策略模式来处理这种变化的可能性。
- 在基本的策略模式中,选择具体的职责由客户端对象承担,并转给策略模式的Context对象。