设计模式2:策略模式

今天来讲一讲“策略模式”(Strategy Pattern)。为什么第二个讲它呢?单例模式第一个讲是我开始以为它最简单(其实并不是),策略模式第二个讲是因为《Head First Design Patterns》这本书第一个讲的就是策略模式。

什么是策略模式?

策略模式就是定义算法族,把它们封装起来,让它们可相互替换,在用的时候再决定用哪一个算法(原文:Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it)。
这个定义可能比较难以理解。以我的理解,所谓策略,就是随机应变,根据具体情况作出最佳选择。
这样做的好处是什么?
最大的好处就是代码灵活。按照《Head First Design Patterns》所说,要把不变的东西保留复用,变的东西抽出来。策略模式中,父类只负责调用接口函数,而不关心具体是怎么实现的,并且利用多态可以自由选择实现接口类。

代码例子

定义一个屠龙勇士对象,应用屠龙接口,这个接口有三个候选策略:近战,远程和法术。
屠龙接口:

@FunctionalInterface
public interface DragonSlayingStrategy {

  void execute();

}

屠龙勇士类:

public class DragonSlayer {

  private DragonSlayingStrategy strategy;

  public DragonSlayer(DragonSlayingStrategy strategy) {
    this.strategy = strategy;
  }

  public void changeStrategy(DragonSlayingStrategy strategy) {
    this.strategy = strategy;
  }

  public void goToBattle() {
    strategy.execute();
  }
}

近战接口实现类(其余两个类似就省略了):

public class MeleeStrategy implements DragonSlayingStrategy {

  private static final Logger LOGGER = LoggerFactory.getLogger(MeleeStrategy.class);

  @Override
  public void execute() {
    LOGGER.info("With your Excalibur you sever the dragon's head!");
  }
}

测试:

public static void main(String[] args) {
    // GoF Strategy pattern
    LOGGER.info("Green dragon spotted ahead!");
    DragonSlayer dragonSlayer = new DragonSlayer(new MeleeStrategy());
    dragonSlayer.goToBattle();
    LOGGER.info("Red dragon emerges.");
    dragonSlayer.changeStrategy(new ProjectileStrategy());
    dragonSlayer.goToBattle();
    LOGGER.info("Black dragon lands before you.");
    dragonSlayer.changeStrategy(new SpellStrategy());
    dragonSlayer.goToBattle();

    // Java 8 Strategy pattern
    LOGGER.info("Green dragon spotted ahead!");
    dragonSlayer = new DragonSlayer(
        () -> LOGGER.info("With your Excalibur you severe the dragon's head!"));
    dragonSlayer.goToBattle();
    LOGGER.info("Red dragon emerges.");
    dragonSlayer.changeStrategy(() -> LOGGER.info(
        "You shoot the dragon with the magical crossbow and it falls dead on the ground!"));
    dragonSlayer.goToBattle();
    LOGGER.info("Black dragon lands before you.");
    dragonSlayer.changeStrategy(() -> LOGGER.info(
        "You cast the spell of disintegration and the dragon vaporizes in a pile of dust!"));
    dragonSlayer.goToBattle();
  }
}

前半部分使用的是之前定义的三个实现类,后半部分使用lambda表达式,其实相当于:

dragonSlayer.changeStrategy(new DragonSlayingStrategy() {
    @Override
    public void execute(){/* 实现   */}
});

是不是很熟悉?对了,安卓里面的OnClick方法不就是这样吗?只不过貌似没有预先定义的点击事件。
也就是说,除了可以在预先定义好的策略里面选择,还可以临时创建一个新的匿名内部类来实现接口。

总结

很多时候,代码的修改和维护占的比重大于代码的初创,因此考虑代码存在的改变可能而事先规划好,某种程度上是能力到一定层次的体现。
策略模式假如使用得当,可以大大提升代码的灵活性。应该好好想一想,哪些代码是在做一个本质的不同方法实现?原来的实现是不是就是把所有方法堆在一起?可不可以抽象出来做一个接口,然后用策略模式来解决?
当代码简单的时候,可能看不出来特别的好处。比如上面的例子,我们当然也可以直接在勇士类里面加入三种屠龙方法。那么好,第四种方法来了呢?再加上。假设现在要有一个法师类,除了只会用法术屠龙以外,别的都和勇士差不多,怎么办?这时候弊端就体现出来了:因为法师不会近战,因此不能继承勇士,代码无法复用。
你可能会说:继承啊没关系,其他方法不用就行了。但这就会造成隐患,因为你就无法阻止别人调用其他三种方法,就可能会造成错误。这时代码就显得不优雅。

参考

https://github.com/iluwatar/java-design-patterns/tree/master/strategy

你可能感兴趣的:(设计模式2:策略模式)