设计模式_2:策略模式

现在出现了一个需求,编写一个超市收银台软件,可以根据商品的价格统计出消费者应付金额,先来没头脑的一顿操作抓狂

import java.util.Scanner;

public class Main {

    private static double total = 0;

    public static void main(String[] args) {

        Scanner scanner = new Scanner(System.in);
        do {
            System.out.println("请输入商品价格:");
            double price = scanner.nextDouble();
            System.out.println("请输入商品数量:");
            int count = scanner.nextInt();
            total += price * count;
            System.out.println("单价:"+price+",数量:"+count+",合计:"+price*count);
            System.out.println("现在总价为"+total);
            System.out.println("还要计算吗? 1继续,0退出");
        } while (scanner.nextInt() == 1);
        System.out.println("程序已退出");
    }
}

运行示例:

请输入商品价格:

1.25
请输入商品数量:
3
单价:1.25,数量:3,合计:3.75
现在总价为3.75
还要计算吗? 1继续,0退出
1
请输入商品价格:
2
请输入商品数量:
3
单价:2.0,数量:3,合计:6.0
现在总价为9.75
还要计算吗? 1继续,0退出
0
程序已退出


简单暴力地完成了任务!突然需求有改,需要增加一个可以为某件商品活动打折的功能,可以再无头脑的干一波:

import java.util.Scanner;

public class Main {

    private static double total = 0;
    private static String[] cashRates = {"正常", "1折", "2折", "3折", "4折", "5折", "6折", "7折", "8折", "9折", "正常"};

    public static void main(String[] args) {

        Scanner scanner = new Scanner(System.in);
        do {
            System.out.println("请输入商品价格:");
            double price = scanner.nextDouble();
            System.out.println("请输入商品数量:");
            int count = scanner.nextInt();
            System.out.println("请输入该商品折扣(0正常,9为9折,8为八折,依次类推):");
            int cashRate = scanner.nextInt();
            cashRate = cashRate == 0 ? 10 : cashRate;
            double sum = price * count * cashRate / 10;
            total += sum;
            System.out.println("折扣:"+cashRates[cashRate]+",单价:"+price+",数量:"+count+",合计:"+sum+",现在总价为"+total);
            System.out.println("还要计算吗? 1继续,0退出");
        } while (scanner.nextInt() == 1);
        System.out.println("程序已退出");
    }
}



运行示例(小数点问题先忽略,主要是为了说策略模式):

请输入商品价格:
1.25
请输入商品数量:
3
请输入该商品折扣(0正常,9为9折,8为八折,依次类推):
8
折扣:8折,单价:1.25,数量:3,合计:3.0,现在总价为3.0
还要计算吗? 1继续,0退出
1
请输入商品价格:
2.3
请输入商品数量:
3
请输入该商品折扣(0正常,9为9折,8为八折,依次类推):
9
折扣:9折,单价:2.3,数量:3,合计:6.209999999999999,现在总价为9.209999999999999
还要计算吗? 1继续,0退出
0
程序已退出


需求又要改了,又要添加一个现金返利活动(像满100返现50的活动),面对频繁修改的需求,就不适合按上面的步骤来改了,于是可以考虑换成上一章节的简单工厂模式来实现一下:

import java.util.Scanner;

public class Main {

    private static double total = 0;

    public static void main(String[] args) {

        Scanner scanner = new Scanner(System.in);
        do {
            System.out.println("请输入该商品活动(normal/rate/benefit):");
            String type = scanner.next();
            System.out.println("输入活动参数param_1:");
            //这里输入两个参数normal可以随便输,rate要输入第一个参数也就是折扣,benefit分别输入返利要求金额和返利金额大小
            double param_1 = scanner.nextDouble();
            System.out.println("输入活动参数param_2:");
            double param_2 = scanner.nextDouble();
            Cash cash = CashFactory.createCash(type, param_1, param_2);
            System.out.println("请输入商品价格:");
            double price = scanner.nextDouble();
            System.out.println("请输入商品数量:");
            int count = scanner.nextInt();
            double sum = cash.acceptCash(price, count);
            total += sum;
            System.out.println("优惠类型:" + cash.getClass().getSimpleName() + ",单价:" + price + ",数量:" + count + ",合计:" + sum + ",现在总价为" + total);
            System.out.println("还要计算吗? 1继续,0退出");
        } while (scanner.nextInt() == 1);
        System.out.println("程序已退出");
    }
}

//抽象的收费手段
abstract class Cash {
    public abstract double acceptCash(double price, int count);
}
//普通收费手段
class NormalCash extends Cash {
    @Override
    public double acceptCash(double price, int count) {
        return price * count;
    }
}
//打折收费手段
class RateCash extends Cash {

    private double rate;

    public RateCash(double rate){
        this.rate = rate;
    }

    @Override
    public double acceptCash(double price, int count) {
        return price * count * rate;
    }
}
//返现收费手段
class BenefitCash extends Cash {

    private double moneyCondition;
    private double moneyReturn;

    public BenefitCash(double moneyCondition, double moneyReturn){
        this.moneyCondition = moneyCondition;
        this.moneyReturn = moneyReturn;
    }

    @Override
    public double acceptCash(double price, int count) {
        if ( (price * count) / moneyCondition > 1 )
            return price * count - (int)((price * count) / moneyCondition) * moneyReturn;
        return price * count;
    }
}
//收费手段类工厂(不要看成现钞工厂( ⊙ o ⊙ ))
class CashFactory {

    public static Cash createCash(String cashType, double... params){
        Cash cash = null;
        switch (cashType) {
            case "normal":
                cash = new NormalCash();
                break;
            case "rate":
                cash = new RateCash((params[0]));
                break;
            case "benefit":
                cash = new BenefitCash(params[0], params[1]);
                break;
            default:
                System.out.println("找不到该优惠活动");
        }
        return cash;
    }
}


运行示例:

请输入该商品活动(normal/rate/benefit):
benefit
输入活动参数param_1:
200
输入活动参数param_2:
50
请输入商品价格:
250
请输入商品数量:
1
优惠类型:BenefitCash,单价:250.0,数量:1,合计:200.0,现在总价为200.0
还要计算吗? 1继续,0退出
1
请输入该商品活动(normal/rate/benefit):
rate
输入活动参数param_1:
0.8
输入活动参数param_2:
0
请输入商品价格:
256
请输入商品数量:
2
优惠类型:RateCash,单价:256.0,数量:2,合计:409.6,现在总价为609.6
还要计算吗? 1继续,0退出
0
程序已退出


简单工厂模式较好地解决了需求频繁更改的情况,但某种算法改动时还是需要改动对应的工厂代码,有没有一种更优雅的方法呢,即使其更松耦合更易维护?该是策略模式出场的时候了:

先写业务逻辑层

//上下文,用来维护一个收费策略的引用
class CashContext {

    private Cash cash;

    public CashContext(Cash cash){
        this.cash = cash;
    }

    public double acceptCash(double price, int count){
        return cash.acceptCash(price, count);
    }
}
//抽象的收费手段
abstract class Cash {
    public abstract double acceptCash(double price, int count);
}
//普通收费手段
class NormalCash extends Cash {
    @Override
    public double acceptCash(double price, int count) {
        return price * count;
    }
}
//打折收费手段
class RateCash extends Cash {

    private double rate;

    public RateCash(double rate){
        this.rate = rate;
    }

    @Override
    public double acceptCash(double price, int count) {
        return price * count * rate;
    }
}
//返现收费手段
class BenefitCash extends Cash {

    private double moneyCondition;
    private double moneyReturn;

    public BenefitCash(double moneyCondition, double moneyReturn){
        this.moneyCondition = moneyCondition;
        this.moneyReturn = moneyReturn;
    }

    @Override
    public double acceptCash(double price, int count) {
        if ( (price * count) / moneyCondition > 1 )
            return price - (int)(price / moneyCondition) * moneyReturn;
        return price;
    }
}

上面代码通过用一个Context类来管理策略类,但是面临一个问题,这样写的话main方法又要和最初那样写一大串switch分支来解决策略的选择了,有一个比较好的方法就是通过策略+工厂模式,改良后的最终代码如下:

import java.util.Scanner;

public class Main {

    private static double total = 0;

    public static void main(String[] args) {

        Scanner scanner = new Scanner(System.in);
        do {
            System.out.println("请输入该商品活动(normal/rate/benefit):");
            String type = scanner.next();
            System.out.println("输入活动参数param_1:");
            //这里输入两个参数normal可以随便输,rate要输入第一个参数也就是折扣,benefit分别输入返利要求金额和返利金额大小
            double param_1 = scanner.nextDouble();
            System.out.println("输入活动参数param_2:");
            double param_2 = scanner.nextDouble();
            CashContext cashContext = new CashContext(type, param_1, param_2);
            System.out.println("请输入商品价格:");
            double price = scanner.nextDouble();
            System.out.println("请输入商品数量:");
            int count = scanner.nextInt();
            double sum = cashContext.acceptCash(price, count);
            total += sum;
            System.out.println("优惠类型:" + cashContext.getCash().getClass().getSimpleName() + ",单价:" + price + ",数量:" + count + ",合计:" + sum + ",现在总价为" + total);
            System.out.println("还要计算吗? 1继续,0退出");
        } while (scanner.nextInt() == 1);
        System.out.println("程序已退出");
    }
}
//上下文,用来维护一个收费策略的引用
class CashContext {

    private Cash cash;

    public CashContext(String cashType, double... params){
        switch (cashType) {
            case "normal":
                this.cash = new NormalCash();
                break;
            case "rate":
                this.cash = new RateCash((params[0]));
                break;
            case "benefit":
                this.cash = new BenefitCash(params[0], params[1]);
                break;
            default:
                System.out.println("找不到该优惠活动");
        }
    }

    public double acceptCash(double price, int count){
        return cash.acceptCash(price, count);
    }

    public Cash getCash() {
        return cash;
    }
}
//抽象的收费手段
abstract class Cash {
    public abstract double acceptCash(double price, int count);
}
//普通收费手段
class NormalCash extends Cash {
    @Override
    public double acceptCash(double price, int count) {
        return price * count;
    }
}
//打折收费手段
class RateCash extends Cash {

    private double rate;

    public RateCash(double rate){
        this.rate = rate;
    }

    @Override
    public double acceptCash(double price, int count) {
        return price * count * rate;
    }
}
//返现收费手段
class BenefitCash extends Cash {

    private double moneyCondition;
    private double moneyReturn;

    public BenefitCash(double moneyCondition, double moneyReturn){
        this.moneyCondition = moneyCondition;
        this.moneyReturn = moneyReturn;
    }

    @Override
    public double acceptCash(double price, int count) {
        if ( (price * count) / moneyCondition > 1 )
            return price * count - (int)((price * count) / moneyCondition) * moneyReturn;
        return price * count;
    }
}

  这样,又将实例化策略的的具体过程由客户端转移到了Context类中

现在对比一下简单工厂模式和策略+工厂模式的代码:

工厂模式:

            Cash cash = CashFactory.createCash(type, param_1, param_2);
策略+工厂模式:

            CashContext cashContext = new CashContext(type, param_1, param_2);
主要区别就在此:
采用策略+工厂模式,main方法中只需认识CashContext就行了,连父类Cash都不需要认识。而纯工厂模式main需要知道Cash和CashFactory两个类。因此采用策略+工厂模式可以进一步实现解耦

总结:

策略模式是一种定义一系列算法的方法,从概念上来看,所有这些算法完成的都是相同的工作,只是实现不同,策略模式可以以相同的方式调用所有的方法,从而减少了算法类和与使用算法类之间的耦合
另外,策略模式可以简化单元测试,因为每个算法都有自己的类,可以通过自己的接口单独测试
      

策略模式就是用来封装算法的,但在实践中,我们发现可以用它来封装几乎任何类型的规则,只要在分析过程中听到需要在不同时间应用不同的业务规则,就可以考虑使用策略模式处理这种变化的可能性



  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值