商场促销-策略模式

商场促销-策略模式

商场收银软件

大鸟给小菜出了一个作业,让小菜做一个商场收银软件,营业员根据客户端所购买商品的单价和数量,向用户收费。

  • 核心代码如下:
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对象。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值