0x01.定义与类型

  • 定义:定义了算法家族,分别封装起来,让它们之间可以相互替换,此模式让算法的变化不会影响到使用算法的用户。
  • 从一系列里抽象出不变的部分
  • 策略模式是将可变的部分从程序中抽象分离成算法接口,在该接口下分别封装一系列算法实现,并使他们可以相互替换,从而导致客户端程序独立于算法的改变。
  • 类型:行为型

0x02.适用场景

  • 系统有很多类,而他们的区别仅仅在于他们的行为不同
  • 一个系统需要动态地在几中算法中选择一种
  • 通过条件语句在多个分支中选取一

0x03.优缺点

1.优点

  • 开闭原则
  • 避免使用多重条件转移语句
  • 提高算法的保密性和安全性

2.缺点

  • 客户端必须知道所以的策略类,并自行决定使用哪一个策略类
  • 产生很多的策略类

0x04.相关的设计模式

  • 策略模式和工厂模式:工厂是创建对象,而策略是具体实现。
  • 策略模式和状态模式:使用策略,客户端是不关心具体状态的。

0x05.策略模式如何实现

1.继承-在父类中提供实现方法,子类通过继承获得父类中的行为

  • 优点:简单易用,已有的类可以快速添加父类的方法
  • 缺点:不具有灵活性,对未来的变更支持差,需要覆写子类的方法来提供新的行为

2.抽象方法-在父类中提供抽象方法,强迫子类实现自己的行为

  • 优点:足够灵活。
  • 缺点:每个子类都要实现一遍代码,即使相同的行为也不例外,代码重复,没有复用的代码。

3.组合-策略模式核心

  • 解释:在类中增加一个私有域,引用另一个已有的类的实例,通过调用引用实例的方法从而获得新的功能,这种设计称作组合(复合)。

将行为抽象为接口,在父类中持有该接口,并由该接口代替接口飞行行为

  • 优点:

    • 足够灵活,复用代码,更易于维护
    • 使用了组合,使架构更加灵活
    • 富有弹性,可以较好的应对变化(开--闭原则)
    • 正好的代码复用型(相对于继承)
    • 消除大量的条件语句
  • 缺点:

    • 客户代码需要了解每个策略实现的细节
    • 增加了对象的数目(维护困难)

4.注意

  • 继承是重用代码的利器,但继承并不是最好的工具。
  • Favor composition over inheritance.(复合优于继承)

0x06.策略模式的实现

  1. 通过分离变化得出的策略接口Strategy
  2. Strategy的实现类
  3. 客户程序中有一个Strategy
  4. 在客户程序中选择/组装正确的Strategy实现

Strategy.png

1.使用策略模式实现超市促销

需求

  • 原价返回
  • 打折
  • 满减~~~~

Promotion.png

  • 策略类(IPromotionStrategy)定义
/**
 * 促销策略类
 */
public interface IPromotionStrategy {

    /**
        * 计算逻辑
        */
    BigDecimal promotionAlgorithm();

    /**
        * 存入价钱
        * @param price
        */
    void setPrice(BigDecimal price);
}
  • 根据不同情况实现策略类
/**
    * 原价
    */
public class CashNormal implements IPromotionStrategy{

    private BigDecimal price;

    @Override
    public BigDecimal promotionAlgorithm() {
        //原价返回
        return this.price;
    }

    public void setPrice(BigDecimal price) {
        this.price = price;
    }
}


/**
    * 打折
    */
public class CashRebate implements IPromotionStrategy {

    private BigDecimal price = BigDecimal.ZERO;

    private BigDecimal rate;

    public CashRebate(BigDecimal rate) {
        this.rate = rate;
    }

    @Override
    public BigDecimal promotionAlgorithm() {
        //TODO 打折逻辑实现
    }

    public void setPrice(BigDecimal price) {
        this.price = price;
    }
}


/**
    * 满减
    */
public class CashReturn implements IPromotionStrategy {

    private BigDecimal price;

    private BigDecimal minPrice;

    private BigDecimal subPrice;

    public CashReturn(BigDecimal minPrice, BigDecimal subPrice) {
        this.minPrice = minPrice;
        this.subPrice = subPrice;
    }

    @Override
    public BigDecimal promotionAlgorithm() {
        //TODO 满减逻辑实现
    }

    public void setPrice(BigDecimal price) {
        this.price = price;
    }
}
  • 创建打折上下文(PromotionContext), 维护使用促销模式
public class PromotionContext {

    /**
        * 策略实现类包
        */
    private static final String PACKAGE_NAME = "org.ko.strategy.promotion";

    /**
        * 组合策略类
        */
    private IPromotionStrategy promotionStrategy;

    /**
        * 获取促销后价钱
        * @return
        */
    public BigDecimal getPrice () {
        return this.promotionStrategy.promotionAlgorithm();
    }

    /**
        * 创建促销手段
        * 这里使用反射维护实例对象, 相关知识这里不再介绍.
        * @param code 对应促销模式编码
        * @param args 对应促销参数
        */
    public void newPromotion (Integer code, Object... args) {
        //根据促销模式编码获取促销模式对应类名称
        String clazz = PromotionType.findClazz(code);
        try {
            //通过反射获取促销模式的对象
            this.promotionStrategy = (IPromotionStrategy)Class.forName(PACKAGE_NAME + "." + clazz)
                    .getDeclaredConstructor(getClasses(args)).newInstance(args);
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
        * 设置打折前的价格
        * @param price
        */
    public void setPrice (BigDecimal price) {
        this.promotionStrategy.setPrice(price);
    }

    /**
        * 获取Class
        * @param args
        * @return
        */
    private Class[] getClasses (Object... args) {
        Class[] classes = new Class[args.length];
        for (int i = 0; i < args.length; i ++) {
            classes[i] = args[i].getClass();
        }
        return classes;
    }

}
  • 创建枚举维护促销样例
/**
    * 促销手段样例
    */
public enum PromotionType {

    CashNormal(1, "CashNormal", "原价"),
    CashRebate(2, "CashRebate", "打折"),
    CashReturn(3, "CashReturn", "满减");

    private Integer code;

    private String clazz;

    private String description;

    PromotionType(Integer code, String clazz, String description) {
        this.code = code;
        this.clazz = clazz;
        this.description = description;
    }

    /**
        * 通过编码获取促销手段
        * @param code 促销手段编码
        * @return
        */
    public static String findClazz(Integer code) {
        for (PromotionType type : PromotionType.values()) {
            if (Objects.equals(code, type.code)) {
                return type.clazz;
            }
        }
        return null;
    }
}
  • 测试与应用
public static void main(String[] args) {
    //初始化上下文
    PromotionContext context = new PromotionContext();

    //测试无促销
    context.newPromotion(1);
    context.setPrice(new BigDecimal("200"));
    BigDecimal price = context.getPrice();
    System.out.println(price);

    //测试打折
    context.newPromotion(2, new BigDecimal("0.8"));
    context.setPrice(new BigDecimal("200"));
    price = context.getPrice();
    System.out.println(price);

    //测试满减
    context.newPromotion(3, new BigDecimal("300"), new BigDecimal("100"));
    context.setPrice(new BigDecimal("200"));
    price = context.getPrice();
    System.out.println(price);
    context.setPrice(new BigDecimal("300"));
    price = context.getPrice();
    System.out.println(price);

}

策略模式总结篇

  1. 将一些方法抽象成接口
  2. 在基类中实例化接口
  3. 设置接口的私有成员变量
  4. 在积累中调用接口的同样方法
  5. 这样实现了代码的复用
  6. 面向接口编程,而不是面向实现编程,多用组合

适用场景

  1. 许多相关的类仅仅是行为差异
  2. 运行时选取不同的算法遍体
  3. 通过条件语句在多个分支中选取一

0x07.源码中的策略模式

  • Comparator, Arrays
  • TreeMap
  • Resource

0x08.代码地址

0x09.推荐


zhiyuan
132 声望6 粉丝

关注技术&商业智能&数字化转型