函數(shù)式編程下的策略模式

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

策略模式的一個(gè)實(shí)現(xiàn)類圖
策略模式的一個(gè)實(shí)現(xiàn)類圖

從該類圖可以看出,基于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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 原文鏈接:https://github.com/EasyKotlin 值就是函數(shù),函數(shù)就是值。所有函數(shù)都消費(fèi)函數(shù),...
    JackChen1024閱讀 6,338評(píng)論 1 17
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,007評(píng)論 25 709
  • 書畫本身是一種高尚的藝術(shù)享受,它可以從書畫藝術(shù)中吸取“營養(yǎng)價(jià)值”很高的精神食糧,來陶冶人們的性情,排除心中不利于健...
    彥子八毛國畫閱讀 282評(píng)論 0 0
  • 貌似不管多繁忙,思想上還是會(huì)偷空放松,偷懶。 盡管知道有很多工作都要做,可還是做不到珍惜時(shí)間,依然會(huì)浪費(fèi)時(shí)間。 一...
    向著太陽奔跑的石頭閱讀 106評(píng)論 1 1

友情鏈接更多精彩內(nèi)容