文章目录

  • 一、策略模式定义
  • 二、策略模式的结构和说明
  • 三、策略模式示例
  • 四、Java8重构策略模式
  • 五、策略模式的优缺点
  • 六、策略模式的应用场景


一、策略模式定义

Define a family of algorithms,encapsulate each one,and make them interchangeable.
定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。

二、策略模式的结构和说明

java 策略模式与工厂模式结合_策略模式

  • Strategy 策略接口,用来约束一些列具体的策略算法。Context使用这个接口来调用具体的策略实现定义的算法。
  • ConcreteStratery 策略实现类,实现具体的算法。
  • Context 上下文,负责和具体的策略类交互。通常上下文会持有一个真正的策略实现,上下文还可以让具体的策略类来获取上下文的数据,甚至让具体的策略类来回调上下文的方法。
三、策略模式示例

假设在我们的商城系统中,需要实现一个优惠促销功能。其中优惠促销有以下几种规则。

  • 新客户:首单立减10元。
  • 满减券:满200,减30。
  • 折扣券:所有商品打9折。
  • … 后续会有更多的优惠规则。

对于这样的一个需求,我们该怎么实现呢?有同学可能会说了,加上几个if else就好了,接着噼里啪啦一顿操作,代码如下就出来了:

/**
 * 购物车
 */
public class ShoppingCart {

    // 购物车中的商品
    private List<Goods> list;

    /**
     * 购物车中商品的总金额
     */
    private BigDecimal amount;


    /**
     * 提交订单主流程
     * @param discountType 选择的优惠券类型
     */
    public void submitOrder(String discountType){

        // 计算优惠减免
        amount = calculate(amount, discountType);

        // 保存订单并付款

        // 送货到家

    }

    /**
     * 计算优惠减免后的金额
     * @param amount    优惠前金额
     * @param discountType  优惠券类型
     * @return 优惠减免后的金额
     */
    public BigDecimal calculate(BigDecimal amount, String discountType){

        if("NEW".equals(discountType)){
            // 计算新用户优惠后的金额
            amount = amount.subtract(BigDecimal.valueOf(10));
        }else if("FULL".equals(discountType)){
            // 计算满减优惠后的金额
            amount = amount.subtract(BigDecimal.valueOf(30));
        }else if("PERCENTAGE".equals(discountType)){
            // 计算折扣优惠后的金额
            amount = amount.multiply(BigDecimal.valueOf(0.9));
        }

        return amount;
    }

}

这种写法是很简单,但仔细想想,这样写可能会有如下方面的问题。

  1. 如果优惠规则多了,类和方法会变得非常庞大,难以维护。而且维护和扩展都需要修改已有的代码,这样违反了开闭原则。
  2. 在不同时期,同样条件下的优惠力度可能也不一样。如在平常时期,新注册用户首单立减10元,在周年庆期间,新注册用户首单立减15元,过了周年庆,又变回首单立减10元。通过上边代码来实现这样的效果,需要频繁的修改if else中的代码,这样的切换方式会非常麻烦。

那么,不卖关子,我们直接来看看,用策略模式是如何解决上边的问题的。

Discount (优惠折扣计算接口),对应模式中的 Strategy (策略接口)

/**
 * 优惠折扣计算接口
 */
public interface Discount {

    /**
     * 计算优惠后的金额
     * @param amount
     * @return 优惠后的金额
     */
    BigDecimal calculate(BigDecimal amount);

}

NewCustomerDiscount(新客户优惠计算类)、FullReductionDiscount(满减券优惠计算类),对应模式中的 ConcreteStratery(策略具体实现类)

/**
 * 新客户优惠,首单立减10元
 */
public class NewCustomerDiscount implements Discount{

    @Override
    public BigDecimal calculate(BigDecimal amount) {
        if(BigDecimal.valueOf(10).compareTo(amount) >= 0){
            return BigDecimal.ZERO;
        }
        return amount.subtract(BigDecimal.valueOf(10));
    }

}
/**
 * 满减优惠,满200减30
 */
public class FullReductionDiscount implements Discount{

    @Override
    public BigDecimal calculate(BigDecimal amount) {
        if(amount.compareTo(BigDecimal.valueOf(200)) >= 0){
            amount = amount.subtract(BigDecimal.valueOf(30));
        }
        return amount;
    }

}

ShoppingCart (购物车类),对应策略模式中的 Context(上下文)

/**
 * 购物车
 */
public class ShoppingCart {

    /**
     * 优惠折扣计算类
     * 这里先演示只能选择一种优惠类型情况,
     * 如果想要支持多种优惠同时使用,可以定义为List<Discount>
     */
    private Discount discount;

    /**
     * 购物车中商品的总金额
     */
    private BigDecimal amount;

    /**
     * 提交订单主流程
     */
    public void submitOrder(){

        // 计算优惠减免
        if(discount != null){
            System.out.println("优惠前总金额:"+amount);
            amount = discount.calculate(amount);
            System.out.println("优惠后总金额:"+amount);
        }


        // 保存订单并付款

        // 送货到家

    }

    /**
     * 设置具体的优惠策略
     * @param discount 优惠策略对象
     */
    public void setDiscount(Discount discount) {
        this.discount = discount;
    }

    /**
     * 模拟输入购物车中物品金额
     * @param amount
     */
    public void setAmount(BigDecimal amount) {
        this.amount = amount;
    }
}

以上,我们就通过策略模式,实现了一个带有优惠券的购物车功能,我们来模拟一下提交购买。

public class Client {

    public static void main(String[] args) {
        // 创建购物车实例
        ShoppingCart shoppingCart = new ShoppingCart();
        // 模拟往购物车中加入总金额为200的商品
        shoppingCart.setAmount(BigDecimal.valueOf(200));
        // 这里创建具体策略实现类我们简单示意一下,一般开发中是需要前端传过来类型,再通过简单工厂等方式获取具体对象
        // 我们要使用的优惠券种类是新客户首单购买优惠券
        Discount discount = new NewCustomerDiscount();
        // 设置优惠策略
        shoppingCart.setDiscount(discount);
        // 提交订单
        shoppingCart.submitOrder();
    }
}

执行main方法后,得到下边的输出:

优惠前总金额:200
优惠后总金额:190

要是想要使用满减优惠,只需要选择满减的优惠策略实现就可以了

Discount discount = new NewCustomerDiscount();
Discount discount = new FullReductionDiscount();

新客户首单优惠和满减优惠已经实现了,那么,如果我们想要扩展优惠策略,例如需要加上折扣券优惠,我们怎么来操作呢?来,看好了哈。

新增 PercentageDiscount (折扣券优惠类)

/**
 * 折扣券优惠,所有商品打9折
 */
public class PercentageDiscount implements Discount{

    @Override
    public BigDecimal calculate(BigDecimal amount) {
        return amount.multiply(BigDecimal.valueOf(0.9));
    }
}

好了,就是这么简单。我们再来模拟一下选用折扣券优惠类型提交购买。

public class Client {

    public static void main(String[] args) {
        // 创建购物车实例
        ShoppingCart shoppingCart = new ShoppingCart();
        // 模拟往购物车中加入了总金额为200的商品
        shoppingCart.setAmount(BigDecimal.valueOf(200));
        // 这里创建具体策略实现类我们简单示意一下,一般开发中是需要前端传过来类型,再通过简单工厂等方式获取具体对象
        // 我们要使用的优惠券种类是折扣券优惠券
        Discount discount = new PercentageDiscount();
        // 设置优惠策略
        shoppingCart.setDiscount(discount);
        // 提交订单
        shoppingCart.submitOrder();
    }
}

执行main方法后,得到下边的输出:

优惠前总金额:200
优惠后总金额:180.0

这样一来,既避免了类或方法变得庞大,又实现了策略的动态切换。维护和扩展起来是不是也方便多了呀。


上边,我们已经演示了扩展新优惠策略的实现方式。那么,如果我们又需要扩展一种较特殊的优惠策略,我们不在金额上做减免,而是给客户的账号里累积积分,客户可以通过积分换购商品,这样该怎么实现呢?来,继续看着哈。

新增 IntegralDiscount (购买商品累积积分类)

/**
 * 购买商品,可以累积同金额的积分,客户可以通过积分换购商品
 */
public class IntegralDiscount implements Discount{

    public IntegralDiscount(String account) {
        this.account = account;
    }

    /**
     * 客户账号
     */
    private String account;

    @Override
    public BigDecimal calculate(BigDecimal amount) {
        System.out.println(String.format("给账号[%s]中加[%d]积分",account,amount.intValue()));
        return amount;
    }
}

再来模拟一下选用累积积分优惠类型提交购买。

public class Client {

    public static void main(String[] args) {
        // 创建购物车实例
        ShoppingCart shoppingCart = new ShoppingCart();
        // 模拟往购物车中加入了总金额为200的商品
        shoppingCart.setAmount(BigDecimal.valueOf(200));
        // 这里创建具体策略实现类我们简单示意一下,一般开发中是需要前端传过来类型,再通过简单工厂等方式获取具体对象
        // 我们要使用的优惠券种类是累积积分类型
        Discount discount = new IntegralDiscount("2020000001");
        // 设置优惠策略
        shoppingCart.setDiscount(discount);
        // 提交订单
        shoppingCart.submitOrder();
    }
}

执行main方法后,得到下边的输出:

优惠前总金额:200
给账号[2020000001]中加[200]积分
优惠后总金额:200

只有这个优惠策略中需要用到客户账号,那就单独在这个策略实现类中添加这个字段好啦。

四、Java8重构策略模式

看到标题,我们很多小伙伴,应该已经想到了什么。对!就是我们Java8新特性之函数式接口和Lambda表达式。好了,忘掉我们上边那么多代码,重新来过。再来定义 ShoppingCart (购物车类)

/**
 * 购物车
 */
public class ShoppingCart {

    /**
     * 优惠折扣计算类,输入一个BigDecimal类型值,返回一个BigDecimal类型值
     * 这里先演示只能选择一种优惠类型情况
     * 如果想要支持多种优惠同时使用,可以定义为LIst<Function<BigDecimal,BigDecimal>>
     */
    private Function<BigDecimal,BigDecimal> discount;

    /**
     * 购物车中商品的总金额
     */
    private BigDecimal amount;


    /**
     * 提交订单主流程
     */
    public void submitOrder(){

        // 计算优惠减免
        if(discount != null){
            System.out.println("优惠前总金额:"+amount);
            amount = discount.apply(amount);
            System.out.println("优惠后总金额:"+amount);
        }


        // 保存订单并付款

        // 送货到家

    }

    /**
     * 设置具体的优惠策略
     * @param discount 优惠策略对象
     */
    public void setDiscount(Function<BigDecimal,BigDecimal> discount) {
        this.discount = discount;
    }

    /**
     * 模拟输入购物车中物品金额
     * @param amount
     */
    public void setAmount(BigDecimal amount) {
        this.amount = amount;
    }
}

好啦,让我们在来模拟一下选用新客户首单立减10元优惠类型提交购买。

public class Client {

    public static void main(String[] args) {
        // 创建购物车实例
        ShoppingCart shoppingCart = new ShoppingCart();
        // 模拟往购物车中加入了总金额为200的商品
        shoppingCart.setAmount(BigDecimal.valueOf(200));
        // 我们要使用的优惠券种类是新客户首单立减10元
        Function<BigDecimal,BigDecimal> discount
                = amount -> BigDecimal.valueOf(10).compareTo(amount) >= 0?
            BigDecimal.ZERO:amount.subtract(BigDecimal.valueOf(10));
        // 设置优惠策略
        shoppingCart.setDiscount(discount);
        // 提交订单
        shoppingCart.submitOrder();
    }
}

执行main方法后,得到下边的输出:

优惠前总金额:200
优惠后总金额:190

从上边代码可以看出,使用Java8新特性实现策略模式,我们就不需要定义具体的策略实现类,通过Lambda表达式避免了采用策略模式时僵化的模板代码。

五、策略模式的优缺点

优点:

  • 可以消除大量的条件语句。
  • 扩展性好,增加一个策略只需实现接口即可。
  • 符合开-闭原则。
  • 算法可以自由切换。

缺点:

  • 策略类需要对外暴露,客户端必须知道所有的策略类,并自行决定使用哪一个策略。
  • 增加了对象数目。
六、策略模式的应用场景
  • 多个类只在算法或行为上稍有不同的场景。
  • 算法需要自由切换的场景。
  • 通过条件语句,在多分支中选取一种。