策略

    策略模式:Strategy,是指,定义一组算法,并把其封装到一个对象中。然后在运行时,可以灵活的使用其中的一个算法。

    策略模式在Java标准库中应用非常广泛,我们以排序为例,看看如何通过实现忽略大小写排序:

    如果我们想忽略大小写排序,就传入String::compareToIgnoreCase,如果我们想倒序排序,就传入(s1, s2) -> -s1.compareTo(s2),这个比较两个元素大小的算法就是策略。

    我们观察Arrays.sort(T[] a, Comparator<? super T> c)这个排序方法,它在内部实现了TimSort排序,但是,排序算法在比较两个元素大小的时候,需要借助我们传入的Comparator对象,才能完成比较。因此,这里的策略是指比较两个元素大小的策略,可以是忽略大小写比较,可以是倒序比较,也可以根据字符串长度比较。

    因此,上述排序使用到了策略模式,它实际上指,在一个方法中,流程是确定的,但是,某些关键步骤的算法依赖调用方传入的策略,这样,传入不同的策略,即可获得不同的结果,大大增强了系统的灵活性。

    策略 - 图1

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

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

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

    满减策略如下:

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

    1. // 持有某个策略:
    2. // 允许客户端设置新策略:
    3. public void setStrategy(DiscountStrategy strategy) {
    4. this.strategy = strategy;
    5. }
    6. public BigDecimal calculatePrice(BigDecimal total) {
    7. return total.subtract(this.strategy.getDiscount(total)).setScale(2);
    8. }

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

    1. ┌───────────────┐ ┌─────────────────┐
    2. └───────────────┘ └─────────────────┘
    3. ┌─────────────────────┐
    4. ├─│UserDiscountStrategy
    5. └─────────────────────┘
    6. ┌─────────────────────┐
    7. ├─│PrimeDiscountStrategy
    8. └─────────────────────┘
    9. ┌─────────────────────┐
    10. └─────────────────────┘

    策略模式的核心思想是在一个计算方法中把容易变化的算法抽出来作为“策略”参数传进去,从而使得新增策略不必修改原有逻辑。

    使用策略模式新增一种策略,允许在满100减20的基础上对Prime会员再打七折。

    从下载练习:策略模式练习 (推荐使用快速下载)

    策略模式是为了允许调用方选择一个算法,从而通过不同策略实现不同的计算结果。

    通过扩展策略,不必修改主逻辑,即可获得新策略的结果。

    策略 - 图2