用Lambda實現模板模式

banq發表於2018-11-07

Java 8 Lambda表示式的簡潔性為經典的GoF設計模式提供了新的視角。透過利用函數語言程式設計,我們可以透過更少的耦合和儀式獲得相同的好處 - 模板方法就是一個很好的例子。

經典的GoF模板方法實現
模板方法設計模式是Gang of Four描述的23種設計模式之一 - 利用它可以輕鬆地符合Open-ClosedHollywood原則。
簡而言之,它有助於定義某個演算法的骨架(演算法不變數),使用者可以填充空白 - 這是透過覆蓋定義骨架實現的抽象類所暴露的抽象方法來實現的。
更實際,想象一些場景,比如記錄某些操作的執行時間,在事務中執行程式碼......或者經典的JUnit工作流,我們只負責以前/後/測試方法的形式填充空白 - 這些是場景圖案閃耀的地方。
讓我們看一個相當簡單的例子,包括用執行時間記錄來包圍我們的程式碼。
經過專業訓練的GoF設計模式從業者將使用抽象類來實現這個想法:

abstract class AbstractTimeLoggingMethod {

    abstract void run();

    public void runWithTimeLogging() {
        var before = LocalTime.now();
        run();
        var after = LocalTime.now();
        System.out.printf(
          "Execution took: %d ms%n", 
          Duration.between(before, after).toMillis());
    }
}

然後,如果我們想用記錄邏輯包裝我們的程式碼片段,我們只需要擴充套件類,然後使用public方法:

public static void main(String[] args) {
    var fetchAndLog = new AbstractTimeLoggingMethod() {
        @Override
        void run() {
            findById(42);
        }
    };

    fetchAndLog.runWithTimeLogging(); // Execution took: 1005 ms
}

然而,由於它依賴於繼承,這種方法非常具有侵入性 - 它將類緊密地耦合在一起並且用過多的樣板程式碼。

用函式簡化模板方法
我們可以在一個接受函式介面的方法中實現,而不是透過使用抽象類來定義骨架:

final class TemplateMethodUtil {

    private TemplateMethodUtil() {
    }

    static void runWithExecutionTimeLogging(Runnable action) {
        var before = LocalTime.now();
        action.run();
        var after = LocalTime.now();
        System.out.printf(
          "Execution took: %d ms%n", 
          Duration.between(before, after).toMillis());
    }
}

現在,只要我們想要傳入函式方法:

TemplateMethodUtil.runWithExecutionTimeLogging(() -> findById(42))​​​​​​​


想要編排多個呼叫?

static void orchestrate(Runnable step1, Runnable step2) {
    System.out.println("starting...");
    step1.run();
    step2.run();
    System.out.println("ending...");
}

呼叫客戶端:

TemplateMethodUtil.orchestrate(
  () -> System.out.println("a"),
  () -> System.out.println("b"));

starting...
a
b
ending...


GoF書中充滿了規範性的想法,但仍然值得重新審視它們,因為新的方法可以實現更好的實現。
程式碼片段可以在GitHub上找到。

 

相關文章