1. 程式人生 > >Java FP: Java中函數語言程式設計的謂詞函式(Predicates)第二部分

Java FP: Java中函數語言程式設計的謂詞函式(Predicates)第二部分

public Iterable<PurchaseOrder> selectOrders(Predicate<? super PurchaseOrder> condition) { 
  
    return Iterables.filter(orders, condition);

}

域驅動設計(Domain-Driven Design)規範

Eric Evans和Martin Fowler一起寫了《模式規範》(譯者注:文章連結已經不存在),很明顯文章描述的就是謂詞。實際上“謂詞”是邏輯程式設計中的名詞,模式規範旨在說明我們如何把邏輯程式設計中的強大功能應用到面向物件程式設計中。

在域驅動設計一書中,Eric Evans詳細說明並給出了描述域模型的規範的例子。如同書中描述的一樣,Policy pattern無非就是Strategy pattern(譯者注:策略模式),也即在某種程度上,Specification pattern(譯者注:規格模式)會選擇帶有額外意圖能夠標識業務規則的不同版本的謂詞指派給域切面。

Specification pattern建議的方法名為:isSatisfiedBy(T): boolean,該方法強調關注域的約束。正如我們前面看到的謂詞,於 Interpreter pattern中類似,封裝在Specification物件中的原子業務邏輯可以通過布林邏輯重組(or, and, not, any, all)。

這本書還描述瞭如資料庫查詢優化、歸類等高階技術。

查詢優化

接下來的內容是一些優化的技巧,我並不確定你是否會在工作中用到。確實,謂詞在過濾資料集的時候顯得有點笨拙:謂詞必須為集合中每個元素都做evaluate操作,當集合非常龐大的情況下,可能會出現效能問題。對於一個給定的謂詞,如果資料都儲存在資料庫中,通過此謂詞一個接一個地獲取龐大集合中的元素並不是一個非常好的主意。

當你遇到了效能問題,你開始分析並且找到了效能瓶頸。如果現在呼叫謂詞過濾資料結構中每個元素是瓶頸的話,你該如何修復這個問題呢?

刪除所有謂詞,把程式碼還原成硬編碼的、更容易出錯、重複率高以及測試難度較高的形式是一種優化手段。但只要我能找到更好的可選方案(總是能找到許多),我會拒絕此項優化。

首先,仔細觀察程式碼是如何使用的。根據域驅動設計的思想,當出現問題時,應當系統審查域物件。

通常在系統中,總是會使用一些清晰的模式。經統計,這些模式為優化提供了許多可能。比如我們的PurchaseOrders 類,在我們假設的例子中,由於業務上的關係,獲取等待處理的訂單將會比其他情況多上許多。

Friend Complicity

基於不同的使用模式,你可能會編寫可選實現類進行某些特定的優化。在我們的例子中,使用者的待處理訂單經常會被查詢,我們為此編寫一個可選實現類FastPurchaseOrder,該類利用一些預先計算好的資料結構提高查詢待處理訂單的速度。

現在,為了從這個可選優化實現中受益,你也許會修改介面,增加一個專用方法,比如selectPendingOrders()。在此之前,介面中只有一個泛型方法,新增額外方法在某些方面是正確的,但同樣會引入一些問題:你必須在其他實現類中也實現這些方法,雖然方法只適用於部分場合而稍顯不合理。

為了讓原先只接受一個謂詞引數的方法在內部自行優化的技巧之一,便是讓方法與謂詞耦合起來。我借鑑了C++中friend關鍵字,把這種方式成為“Friend Complicity”。

/** Optimization method: pre-computed list of pending orders */

private Iterable<PurchaseOrder> selectPendingOrders() {

    // ... optimized stuff...

}

public Iterable<PurchaseOrder> selectOrders(Predicate<? super PurchaseOrder> condition) {

    // internal complicity here: recognize friend class to enable optimization

    if (condition instanceof PendingOrderPredicate) {

        return selectPendingOrders();// faster way

    }

    // otherwise, back to the usual case

    return Iterables.filter(orders, condition);

}

很明顯,這種方式提高了不同類之間的耦合度,這些類理應互不依賴。同時,只能在直接提供“friend”謂詞的情況下提高效能,不能帶有裝飾或者組合模式。

Friend Complicity最重要的一點是,確保這些特定方法不做任何妥協,需要在任何時候都遵循介面的規範(即使在不需要效能提升的情況下)。同時,請記住,將來或許有一天你可能會將實現變更會未優化的實現版本。

SQL-compromised

如果訂單儲存在資料庫中,可以通過SQL快速查詢。順便提一下,你可能已經注意到謂詞正是你的SQL中where子句後的查詢條件。

一個簡單的依舊使用謂詞提升效能的方式是,實現額外的介面SqlAware,該介面帶有一個SQLasSQL()方法,方法返回謂詞實際執行時操作資料庫的SQL。當對資料庫使用這類謂詞時,謂詞會直接使用SQL查詢並返回結果,不會執行常規evaluate或者apply 方法。

我把這種謂詞入侵資料庫底層細節的方法稱為SQL妥協,通常情況下謂詞不應該這麼做。

歸類

歸類是一種描述了概念間包含關係的邏輯概念。比如,紅,綠,黃,包含於術語“色彩”中。謂詞間的歸類可以成為程式碼中非常強大的實現工具。

我們來舉一個廣播股票價格的應用程式的例子。在註冊的時候,我們必須宣告對哪類股票的觀察比較感興趣。我們只需要簡單傳遞當操作到我們感興趣的股票時返回true的股票類謂詞:

public final class StockPredicate implements Predicate<String> {

    private final Set<String> tickers;

    // Constructors omitted for clarity

    public boolean apply(String ticker) {

        return tickers.contains(ticker);

    }

}

現在我們假設應用程式已經可以利用訊息主題廣播標準的股票程式碼集合,每一個主題都擁有相應的謂詞。如果檢測到我們將要使用的謂詞,是被包含於或者被歸類為某個標準謂詞,我們就可以訂閱它。在我們的例子中,歸類是一個非常簡單的工作,只需要在我們的謂詞中新增額外的方法即可:

public boolean encompasses(StockPredicate predicate) {

    return tickers.containsAll(predicate.tickers);

}

歸類操作主要關注謂詞間的包含關係。就如例子中一樣,基於集合設計的謂詞非常容易實現歸類操作,基於內部數字和日期實現的謂詞也同樣如此。否則,你可能需要求助於“Friend Complicity”,需要了解其他謂詞以便判定謂詞間的包含關係。

總之,在通常情況下,歸類是難以實現的。但即便只是實現了部分歸類,也能帶來巨大的價值。所以說歸類是一個很重要的工具。

總結

謂詞很有趣,能夠提升程式碼質量,改進思維模式。

參考文獻