设计模式_2:策略模式

来源:互联网 发布:linux ln 删除 编辑:程序博客网 时间:2024/05/17 23:31

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

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两个类。因此采用策略+工厂模式可以进一步实现解耦

总结:

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

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



原创粉丝点击