在生活中,常常遇到实现某种目标存在多种策略可供选择的情况,如:出行旅游可以乘飞机、坐火车、骑单车或者开车等,超市促销可以采用打折、送商品、送积分等方法。
在软件开发中也常常遇到类似的情况,当实现某一个功能的时候存在多种算法或者策略,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成该功能,如数据排序策略有冒泡排序、选择排序、插入排序、二叉树排序等。
如果使用多重条件转移语句实现(即硬编码),不但使条件语句变得很复杂,而且增加、删除或者更换算法都要修改原代码,不易维护,违背开闭原则。如果采用策略模式就能很好解决该问题。

定义

策略(Strategy)模式的定义:该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。

优点

  1. 多重条件语句不易维护,而使用策略模式可以避免使用多重条件,如 if…else 语句、switch…case 语句
  2. 策略模式可以提供相同行为的不同实现,客户可以根据不同时间或空间要求选择不同的
  3. 策略模式提供了对开闭原则的完美支持,可以在不修改原代码的情况下,灵活增加算法
  4. 策略模式把算法的使用放到环境类中,而算法的实现转移到具体策略类中,实现了二者的分离

    结构

    策略模式是准备一组算法,并将这组算法封装到一系列的策略类里面,作为一个抽象策略类的子类。策略模式的重心不是如何实现算法,而是如何组织这些算法,从而让程序结构更加灵活,具有更好的维护性和扩展性。
    策略模式中的主要角色:

  5. 抽象策略(Strategy)类:定义一个公共接口,各种不同的算法以不同的方式实现这个接口,环境角色使用这个接口调用不同的算法,一般使用接口或者抽象类实现

  6. 具体策略类(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现
  7. 环境(Context)类:持有一个策略类的引用,最终给客户端调用

策略模式 - 图1

应用实例

一个完整的策略模式要定义策略以及使用策略的上下文。我们以购物车结算为例,假设网站针对普通会员、Prime会员有不同的折扣,同时活动期间还有一个满100减20的活动,这些就可以作为策略实现。
先定义打折策略接口:

  1. public interface DiscountStrategy {
  2. BigDecimal getDiscount(BigDecimal total);
  3. }

接下来,就是实现各种策略。普通用户策略如下:

  1. public class UserDiscountStrategy implements DiscountStrategy {
  2. @Override
  3. public BigDecimal getDiscount(BigDecimal total) {
  4. // 普通会员打九折
  5. return total.multiply(new BigDecimal("0.1")).setScale(2, RoundingMode.DOWN);
  6. }
  7. }

满减策略如下:

  1. public class OverDiscountStrategy implements DiscountStrategy {
  2. @Override
  3. public BigDecimal getDiscount(BigDecimal total) {
  4. // 满100减20优惠
  5. return total.compareTo(BigDecimal.valueOf(100)) >= 0 ? BigDecimal.valueOf(20) : BigDecimal.ZERO;
  6. }
  7. }

最后,要应用策略,我们需要一个 DiscountContext

  1. public class DiscountContext {
  2. private DiscountStrategy discountStrategy = new UserDiscountStrategy();
  3. public void setDiscountStrategy(DiscountStrategy discountStrategy) {
  4. this.discountStrategy = discountStrategy;
  5. }
  6. public BigDecimal calculateAmount(BigDecimal total) {
  7. return total.subtract(discountStrategy.getDiscount(total)).setScale(2, RoundingMode.DOWN);
  8. }
  9. }

调用方必须首先创建一个 DiscountContext,并指定一个策略(或者使用默认策略),即可获得折扣后的价格:

  1. public static void main(String[] args) {
  2. DiscountContext ctx = new DiscountContext();
  3. BigDecimal pay1 = ctx.calculateAmount(BigDecimal.valueOf(105));
  4. System.out.println(pay1);
  5. ctx.setDiscountStrategy(new OverDiscountStrategy());
  6. BigDecimal pay2 = ctx.calculateAmount(BigDecimal.valueOf(105));
  7. System.out.println(pay2);
  8. }

扩展

在一个使用策略模式的系统中,当存在的策略很多时,客户端管理所有策略算法将变得很复杂,如果在环境类中使用策略工厂模式来管理这些策略类将大大减少客户端的工作复杂度。
策略模式 - 图2