设计模式--策略模式

设计模式--策略模式

上篇聊完了简单工厂模式,这篇来说说策略模式。那么还是以一个例子来进行说明。

某公司中要开发一个商场收银的软件,由小明进行开发。以下是他的代码详情:

java 复制代码
public class Test {
    public static void main(String[] args) {
        double price;  // 商品单价
        int num;        // 商品数量
        double totalPrice; // 当前商品合计费用
        double total = 0d;      // 总计商品合计费用
        Scanner sc = new Scanner(System.in);
        do {
            System.out.println("输入商品的销售模式:1.原价 2.八折 3.七折");
            int discount = Integer.parseInt(sc.nextLine());
            System.out.println("输入商品单价:");
            price = Double.parseDouble(sc.nextLine());
            System.out.println("输入商品数量:");
            num = Integer.parseInt(sc.nextLine());
            System.out.println();
            if (price > 0 && num > 0) {
                // 计算当前金额和总金额
                totalPrice = price * num;
                total = total + totalPrice;
                System.out.println("单价:" + price + "元 数量:" + num + " 合计:" + totalPrice + "元");
                System.out.println("总计:" + total " 元");
            }
        } while (price > 0 && num > 0);
    }
}

完事儿后提交给Leader,Leader看完后摇了摇头,你这个确实能实现我要求的功能,但是当我需要做活动,打折时怎么办呢?

小明一听,这不简单,我根据商品的销售模式增加代码就行了。

java 复制代码
int discount = Integer.parseInt(sc.nextLine()); // 接收到商品销售模式的参数

// 计算当前金额和总金额
switch (discount) {
    case 1:
        totalPrice = price * num;
        break;
    case 2:
        totalPrice = price * num * 0.8;
        break;
    case 3:
        totalPrice = price * num * 0.7;
        break;
}
total = total + totalPrice;

Leader看完后还是摇了摇头,说:"这比刚才确实灵活性上好多了,不过重复的代码很多啊,比如这3个case分支要执行的语句除了打折多少以外几乎没有什么不同的了,应该考虑重构一下了。不过这还不是主要的,现在我又有新的需求:需要满300返100的促销算法,怎么办呢?"

小明想了想,突然灵光一现,好像可以使用简单工厂模式啊,加了功能又不影响全局的代码。说干就干

使用简单工厂

又建了几个类:

  • 收费的抽象类
java 复制代码
public abstract class CashSuper {
    // 接收价格和数量参数
    public abstract double acceptCash(double price, int num);
}
  • 正常收费类
java 复制代码
public class CashNormal extends CashSuper {
    @Override
    public double acceptCash(double price, int num) {
        // 原价
        return price * num;
    }
}
  • 打折收费类
java 复制代码
public class CashRebate extends CashSuper {    
    private double moneyRebate = 1d;
    // 初始化时必须输入打折的折扣,八折就是0.8
    public CashRebate(double moneyRebate){
        this.moneyRebate = moneyRebate;
    } 
    @Override
    public double acceptCash(double price, int num) {
        // 打折
        return price * num * moneyRebate;
    }
}
  • 返现类
java 复制代码
public class CashReturn extends CashSuper {

    private double moneyCondition = 0d; // 返利条件
    private double moneyReturn = 0d;    // 返回金额

    // 返利收费,初始化时必须输入返利条件和返利值
    // 如300返100,moneyCondition = 300 , moneyReturn = 100
    public CashReturn(double moneyCondition, double moneyReturn) {
        this.moneyCondition = moneyCondition;
        this.moneyReturn = moneyReturn;
    }
    @Override
    public double acceptCash(double price, int num) {
        // 满足条件时,原价 - 返现值
        double result = price * num;
        if (moneyCondition > 0 && result >= moneyCondition) {
            result = result - Math.floor(result / moneyCondition) * moneyReturn;
        }
        return result;
    }
}
  • 收费工厂(确认收费模式)
java 复制代码
public class CashFactory { 
    public static CashSuper createCashAccept(int cashType) {
        CashSuper cs = null;
        switch (cashType) {
            case 1:
                cs = new CashNormal();//正常收费
                break;
            case 2:
                cs = new CashRebate(0.8d); // 八折
                break;
            case 3:
                cs = new CashRebate(0.7d); // 七折
                break;
            case 4:
                cs = new CashReturn(300d, 100d);    // 满300返100
                break;
        }
        return cs;
    }
   
}

客户端调用代码:

java 复制代码
public class Test {

    public static void main(String[] args) {
        double price;  // 商品单价
        int num;        // 商品数量
        double totalPrice = 0d; // 当前商品合计费用
        double total = 0d;      // 总计商品合计费用
        Scanner sc = new Scanner(System.in);
        do {
            System.out.println("输入商品的销售模式:1.原价 2.八折 3.七折");
            int discount = Integer.parseInt(sc.nextLine());

            System.out.println("输入商品单价:");
            price = Double.parseDouble(sc.nextLine());
            System.out.println("输入商品数量:");
            num = Integer.parseInt(sc.nextLine());

            CashSuper cashAccept = CashFactory.createCashAccept(discount);
            totalPrice = cashAccept.acceptCash(price, num);
            total += totalPrice;

        } while (price > 0 && num > 0);
    }

}

搞定了,这次无论怎么改,都可以进行简单处理,就算增加新的活动,直接创建新的活动类实现自己的业务算法就行了。

Leader看后点了点头,重构了一波代码确实看上去好多了。随即又问:"假如我现在新增了一个促销手段,积分积累满100积分10点,积累到一定积分可以兑换礼品。虽然你可以用这个简单工厂模式,但是这个模式只是能解决对象的创建问题,而且由于工厂本身包括所有的收费方式,商场是可能经常性更改打折额度和返利额度,每次维护和扩展收费方式,你都需要改动这个对象工厂,以导致代码需要重新编译部署,真的是很糟糕的处理方式,所以这个不是最好的方法。面对算法的时长变动,应该会有更好的办法的。"

策略模式

次日小明找到了合适的模式--策略模式:它定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的客户。

商场收银时如何促销,用打折还是返利,其实都是一些算法,用工厂来生成算法对象,这没有问题,但是算法本身只是一种策略,最重要的是这些算法是随时可能互相替换的,这就是变化点,而封装变化点则是我们面向对象的一种很重要的思维方式。

策略模式概要

看下策略模式架构

大致理解

  • Strategy类,定义所有支持算法的公共接口
java 复制代码
public abstract class Strategy {
    
    // 算法方法
    public abstract void algorithmInterface();
    
}
  • ConcreteStrategy类,封装具体的算法和行为,继承Strategy
java 复制代码
public class ConcreteStrategyA extends Strategy {
    @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对象的引用
java 复制代码
public class Context {

    private Strategy strategy;

    // 初始化时,传入具体的策略对象
    public Context(Strategy strategy) {
        this.strategy = strategy;
    }
    // 上下文接口
    public void contextInterface() {
        // 根据具体的策略对象,调用其算法的方法
        strategy.algorithmInterface();
    }
}

客户端代码:

java 复制代码
Context context;
context = new Context(new ConcreteStrategyA());
context.contextInterface();
context = new Context(new ConcreteStrategyB());
context.contextInterface();
context = new Context(new ConcreteStrategyC());
context.contextInterface();

实例化不同的策略对象,调用不同的策略算法。那么实际中如何使用呢?

实现策略模式

实际上,前面案例中所写的几个类就是策略的算法类了,CashSuper类是抽象策略,正常收费CashNormal、打折收费CashRebate和返利收费CashReturn就是三个具体策略,也就是策略模式中说的具体算法。

那么现在只需要增加一个Context的类就行了。

java 复制代码
public class CashContext {
    private CashSuper cs;

    public CashContext(CashSuper cs) {
        this.cs = cs;
    }
    public double getResult(double price, int num) {
        return cs.acceptCash(price, num);
    }
}

客户端主要代码:

java 复制代码
CashContext cc = null;
switch (cashType) {
    case 1:
        cc = new CashContext(new CashNormal());//正常收费
        break;
    case 2:
        cc = new CashContext(new CashRebate(0.8d)); // 八折
        break;
    case 3:
        cc = new CashContext(new CashRebate(0.7d)); // 七折
        break;
    case 4:
        cc = new CashContext(new CashReturn(300d, 100d));    // 满300返100
        break;
}
totalPrice = cc.getResult(price, num)l;
total += totalPrice;

写出来后,发现其实好像和工厂模式没什么两样啊,Leader又问道:"你有没有办法将这个判断从客户端这里转移走呢,让工厂来帮你判断你需要生成的算法对象呢?难道简单工厂模式必须是一个单独的类吗,难道不可以和策略模式一起用吗?"

策略与简单工厂结合

小明恍然大悟,原来还可以这样做,马上开始改造代码:

java 复制代码
public class CashContext {
    private CashSuper cs;

    // 通过构造方法传入收费策略
    public CashContext(int cashType) {
        CashSuper cs = null;
        switch (cashType) {
            case 1:
                this.cs = new CashNormal();//正常收费
                break;
            case 2:
                this.cs = new CashRebate(0.8d); // 八折
                break;
            case 3:
                this.cs = new CashRebate(0.7d); // 七折
                break;
            case 4:
                this.cs = new CashReturn(300d, 100d);    // 满300返100
                break;
        }
    }

    public CashContext(CashSuper cs) {
        this.cs = cs;
    }
    public double getResult(double price, int num) {
        return cs.acceptCash(price, num);
    }
}

客户端调用时

java 复制代码
CashContext cc = new CashContext(discount);
totalPrice = cc.getResult(price, num);
total += totalPrice;

Leader看后很满意,说道:"这样子不就对了,简单工厂模式并非只有建一个工厂类的做法,和策略模式一起用起来,不就省了更多事儿了吗。你刚开始的简单工厂也没问题,能解决具体需要哪个算法对象的问题,但是客户端调用时,他是不是必须要认识你的CashSuper啊和CashFactory类呢。但是现在你把策略模式和简单工厂一结合。客户端调用时只需要认识你的CashContext类,其他什么都不用管。耦合度是不是更低了。"

总结

策略模式是一种定义一系列算法的方法,从概念上看,所有这些算法都是完成相同的工作,只是实现不同,它可以以相同的方式调用所有的算法,减少了各种算法类和使用算法类之间的耦合度。

那么除此之外,策略模式能简化单元测试,每个算法都有自己的类,测试时只需要实例化这个算法类给上参数,就能直接测试,修改其中任意一个算法类也不会对其他算法类有影响。

在基本的策略模式中,选择所用具体实现的职责由客户端对象承担,并且转给策略模式的Context对象。这本身没有解除客户端需要选择判断的压力,而策略模式和简单工厂模式结合后,选择具体实现的职责也由Context来承担,这就最大化减轻了客户端的职责。

相关推荐
忒可君26 分钟前
C# winform 报错:类型“System.Int32”的对象无法转换为类型“System.Int16”。
java·开发语言
斌斌_____41 分钟前
Spring Boot 配置文件的加载顺序
java·spring boot·后端
路在脚下@1 小时前
Spring如何处理循环依赖
java·后端·spring
一个不秃头的 程序员1 小时前
代码加入SFTP JAVA ---(小白篇3)
java·python·github
丁总学Java1 小时前
--spring.profiles.active=prod
java·spring
上等猿2 小时前
集合stream
java
java1234_小锋2 小时前
MyBatis如何处理延迟加载?
java·开发语言
菠萝咕噜肉i2 小时前
MyBatis是什么?为什么有全自动ORM框架还是MyBatis比较受欢迎?
java·mybatis·框架·半自动
林的快手2 小时前
209.长度最小的子数组
java·数据结构·数据库·python·算法·leetcode
zh路西法2 小时前
【C++决策和状态管理】从状态模式,有限状态机,行为树到决策树(二):从FSM开始的2D游戏角色操控底层源码编写
c++·游戏·unity·设计模式·状态模式