策略模式是我們常用的一種設(shè)計(jì)模式。
它屬于對(duì)象的行為模式。其用意是針對(duì)一組算法,將每一個(gè)算法封裝到具有共同接口的獨(dú)立的類中,從而使得它們可以相互替換。策略模式使得算法可以在不影響到客戶端的情況下發(fā)生變化。
具體的策略模式原理和實(shí)現(xiàn)方法,在此不詳述,不熟悉的讀者可以查看github的該專題-iluwatar/java-design-patterns。
其中有一個(gè)典型的例子,其類圖如下所示:

從該類圖可以看出,基于Java對(duì)象的策略模式實(shí)現(xiàn)起來,比較復(fù)雜,需要一個(gè)策略接口(DragonSlayingStrategy)和三個(gè)具體的實(shí)現(xiàn)(ProjectileStrategy、meleeStrategy、SpellStrategy)。
在Java8以前的這樣純面向?qū)ο蟮恼Z言中,這樣實(shí)現(xiàn)策略模式,是迫不得已的方法。其實(shí),根本原因就在于函數(shù)在純面向?qū)ο蟮恼Z言中不能獨(dú)立存在,它只能依附于一個(gè)接口,然后通過實(shí)現(xiàn)接口來具體實(shí)現(xiàn)。
由此,我們可以想到,在函數(shù)式編程模式下,由于函數(shù)可以以lambda表達(dá)式獨(dú)立存在,那么,我們的策略模式實(shí)現(xiàn)起來,是不是就不一樣了。
下面,我們來看看基于函數(shù)式編程下的策略模式的實(shí)現(xiàn)。
首先,我們可以想到,“DragonSlayingStrategy”接口在函數(shù)式編程模式下,可以作為一個(gè)函數(shù)式接口。
其次,三個(gè)策略的具體實(shí)現(xiàn),也不必依賴于實(shí)體類了。
DragonSlayingStrategy接口的實(shí)現(xiàn)大概就是下面的樣子了:
@FunctionalInterface
public interface DragonSlayingStrategy {
void execute();
}
多了這么一個(gè)函數(shù)式接口,是因?yàn)镴ava8的函數(shù)式接口中,只有Supplier、Consumer、Function等少量的幾個(gè),更多的需要我們自己實(shí)現(xiàn)。如果用到了生產(chǎn)者、消費(fèi)者或Function這幾個(gè)接口,那么連策略的接口都可以不用實(shí)現(xiàn)了。
接著,我們來實(shí)現(xiàn)策略,我們可以把所有的策略放在一個(gè)類里實(shí)現(xiàn),減少策略類的個(gè)數(shù):
public class StrategyType {
private static final Logger LOGGER = LoggerFactory.getLogger(StrategyType.class);
public static DragonSlayingStrategy getStrategy(String type) {
if ("MeleeStrategy".equals(type)) return MELEE_STRATEGY;
else if ("ProjectileStrategy".equals(type)) return PROJECTILE_STRATEGY;
else if ("SpellStrategy".equals(type)) return SPELL_STRATEGY;
else return MELEE_STRATEGY;
}
private static DragonSlayingStrategy MELEE_STRATEGY = () -> {LOGGER.info("With your Excalibur you sever the dragon's head!");};
private static DragonSlayingStrategy PROJECTILE_STRATEGY = () -> {LOGGER.info("You shoot the dragon with the magical crossbow and it falls dead on the ground!");};
private static DragonSlayingStrategy SPELL_STRATEGY = () -> {LOGGER.info("You cast the spell of disintegration and the dragon vaporizes in a pile of dust!");};
}
最后是DragonSlayer類,它基本上變化不大:
public class DragonSlayer {
private static final Logger LOGGER = LoggerFactory.getLogger(DragonSlayer.class);
private DragonSlayingStrategy strategy;
public DragonSlayer(DragonSlayingStrategy strategy) {
this.strategy = strategy;
}
public void changeStrategy(DragonSlayingStrategy strategy) {
this.strategy = strategy;
}
public void goToBattle() {
this.strategy.execute();
}
}
測試代碼如下所示:
public class App {
private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
public static void main(String[] args) {
// GoF Strategy pattern
LOGGER.info("Green dragon spotted ahead!");
DragonSlayer dragonSlayer = new DragonSlayer(getStrategy("MeleeStrategy"));
dragonSlayer.goToBattle();
LOGGER.info("Red dragon emerges.");
dragonSlayer.changeStrategy(getStrategy("ProjectileStrategy"));
dragonSlayer.goToBattle();
LOGGER.info("Black dragon lands before you.");
dragonSlayer.changeStrategy(getStrategy("SpellStrategy"));
dragonSlayer.goToBattle();
}
}
可以看到,函數(shù)式編程確實(shí)大大的減少了模式實(shí)現(xiàn)的類個(gè)數(shù)和代碼量,同時(shí),也大大增加了代碼實(shí)現(xiàn)的靈活度。在后面其他模式的函數(shù)式編程實(shí)現(xiàn)中,我們還會(huì)繼續(xù)看到這些效果。
實(shí)際上,如果某些策略只在某個(gè)客戶端代碼中有用,在其他地方不太用到,那么,我們的策略代碼也可以轉(zhuǎn)移到客戶端中實(shí)現(xiàn),這樣,StrategyType類都沒有存在的意義了:
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();
參考文獻(xiàn):iluwatar/java-design-patterns