今天来讲一讲“策略模式”(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