用函式正規化實現戰略模式

banq發表於2019-01-30

戰略模式又稱為策略模式,其目的是讓我們能使用不同但可互換的演算法。現在我們在另一個實際例子中使用這種模式。我們想要概括一個流程,該流程在輸入中獲取文字,使用給定的條件對其進行過濾,並在最終格式化或轉換後將其列印在標準輸出上。換句話說,我們需要概括2個行為:一個過濾文字,另一個轉換它。第一步是將這兩種行為的抽象定義放入一個介面中。

interface TextFormatter {
    boolean filter(String text);
    String format(String text);
}


在此之後,可以開發一個實現一般流程的類來釋出一個文字,該文字與TextFormatter介面(策略)的例項一起傳遞,該例項又封裝了使用者想要如何過濾和格式化文字的細節。

public class TextEditor {
    private final TextFormatter textFormatter;
 
    public TextEditor(TextFormatter textFormatter) {
        this.textFormatter = textFormatter;
    }
 
    public void publishText(String text) {
       if (textFormatter.filter( text )) {
           System.out.println( textFormatter.format( text ) );
        }
    }
}

我們現在可以提供策略的多個實現。最明顯的一個接受任何文字並按原樣列印。

public class PlainTextFormatter implements TextFormatter {
 
    @Override
    public boolean filter( String text ) {
        return true;
    }
 
    @Override
    public String format( String text ) {
        return text;
    }
}


另一個可以用於逐行解析日誌檔案以搜尋錯誤訊息,因此它只接受以“ERROR”開頭的句子並以大寫形式列印它們。

public class ErrorTextFormatter implements TextFormatter {
 
    @Override
    public boolean filter( String text ) {
        return text.startsWith( "ERROR" );
    }
 
    @Override
    public String format( String text ) {
        return text.toUpperCase();
    }
}


最後,我們可以更具想象力,以小寫字母列印僅短於20個字元的文字。

public class ShortTextFormatter implements TextFormatter {
 
    @Override
    public boolean filter( String text ) {
        return text.length() < 20;
    }
 
    @Override
    public String format( String text ) {
        return text.toLowerCase();
    }
}

此時,我們可以建立一個TextEditor,傳遞一個TextFormatter例項,該例項特定於我們想要執行的文字釋出型別。

TextEditor textEditor = new TextEditor( new ErrorTextFormatter() );
textEditor.publishText( "ERROR - something bad happened" );
textEditor.publishText( "DEBUG - I'm here" );


到目前為止一直很好,但再一次感覺這種實現比它更加冗長。這一次,唯一相關的訊號是TextEditor類的publishText中實現的邏輯。TextFormatter介面定義的2個行為可以使用2個函式傳遞給publishText方法:一個謂詞來過濾要釋出的測試;另外一個是UnaryOperator(一個函式轉換另一個相同型別的物件)來格式化之前將其傳送到標準輸出。

public static void publishText( String text, Predicate<String> filter, UnaryOperator<String> format) {
    if (filter.test( text )) {
        System.out.println( format.apply( text ) );
    }
}


透過這種方式,我們可以按原樣釋出任何文字,如下所示由PlainTextFormatter完成:

publishText( "DEBUG - I'm here", s -> true, s -> s );


或者可以重新實現ErrorTextFormatter提供的功能,傳遞一個僅接受以“ERROR”開頭的文字的Predicate和一個將String轉換為大寫的函式。

publishText( "ERROR - something bad happened", s -> s.startsWith( "ERROR" ), String::toUpperCase );


對這種更緊湊的解決方案的一個常見反對意見是,在類中顯式實現TextFormatter允許擁有一個可以重用的策略庫,而不是為每個單獨的呼叫重複它們的實現。然而,沒有什麼能阻止我們對函式採用類似的方法並將它們收集在這樣一個合適的庫中。

public class TextUtil {
    public boolean acceptAll(String text) {
        return true;
    }
 
    public String noFormatting(String text) {
        return text;
    }
 
    public boolean acceptErrors(String text) {
        return text.startsWith( "ERROR" );
    }
 
    public String formatError(String text) {
        return text.toUpperCase();
    }
}


透過這種方式,我們可以重用定義的函式,而不是使用匿名lambdas:

publishText( "DEBUG - I'm here", TextUtil::acceptAll, TextUtil::noFormatting );

publishText( "ERROR - something bad happened", TextUtil::acceptErrors, TextUtil::formatError );


值得注意的是,這些函式實際上比策略類更精細(它們可以以任何類不可用的方式組合),並允許更好的可重用性。

 

相關文章