如何學習Java的規則引擎模式? - plagov

banq發表於2022-02-11

在這篇博文中,我想描述一下我是如何在為開源專案做出貢獻的同時瞭解規則引擎模式的。
在我作為測試自動化工程師的工作中,我一直在使用 Selenide。所以,當我必須完成某項任務時,我發現 Selenide 沒有解決方案來幫助我。所以,我認為這可能是一個為開源庫貢獻新功能的好機會。
 

問題
我遇到的問題是這樣的。Selenide庫有一個函式,可以在當前元素的HTML DOM中找到一個父節點。這個父節點是同一個HTML元素,所以它有不同的屬性,可以用來在頁面上定位它--一個標籤名、一個類名、一個屬性、一個有值的屬性。所以,我們的任務是為每個選項建立一個XPath表示式。

一個非常直接的解決方案是做一個if - else if - else語句。但有了上面提到的四個選項,這種結構就顯得太草率了。一定有一個更好、更乾淨的方法來完成這個任務。
 

規則引擎模式
對於這種問題,確實有一種更好的方法--規則引擎模式。這種模式的本質是將每一個if - else if - else分支分割在其規則類中。然後,主規則引擎類將持有所有的規則,並找到符合客戶要求的規則。
 

定義一個規則類
為了確保所有的規則類將實現相同的方法,讓我們定義一個介面,每個類將實現這個介面。

public interface AncestorRule {
  Optional<AncestorResult> evaluate(String selector);
}


接下來,讓我們定義第一個規則類。該類將容納if-else分支中定義的邏輯:

public class AncestorWithClassRule implements AncestorRule {

  @Override
  public Optional<AncestorResult> evaluate(String selector) {
    if (isCssClass(selector)) {
      String xpath = format(
        "ancestor::*[contains(concat(' ', normalize-space(@class), ' '), ' %s ')][%s]",
        selector.substring(1)
      );
      return Optional.of(new AncestorResult(xpath));
    }
    return Optional.empty();
  }
}

所以,這裡是檢查給定選擇器是否符合給定條件的單一邏輯--如果它是一個CSS類。isCssClass()是一個定義在supper類中的函式(為了簡潔起見,這裡沒有顯示)。如果選擇器確實是一個CSS類,那麼它將建立一個XPath表示式,並將其作為AncestorResult的一個Optional返回,否則就是一個空Optional。

這個規則類是乾淨的、簡短的、容易理解的。它只需寫一次,不需要經常修改,除非規則的業務邏輯被更新。

我們以同樣的方式定義其他規則。如果輸入符合給定的條件,就驗證,建立並返回相應的XPath表示式。否則,就是一個空的結果。
 

規則結果
上面的程式碼有AncestorResult的用法。這個類的目的是包裝成功評估計算的結果。這個類看起來如下。

public class AncestorResult {

  private final String value;

  public AncestorResult(String value) {
    this.value = value;
  }

  public String getValue() {
    return value;
  }
}


只有一個類的欄位,我們透過建構函式來設定,用getter來訪問它。
 

規則引擎類
現在,讓我們最後來看看持有規則引擎邏輯的類。

public class AncestorRuleEngine {

    private static final List<AncestorRule> rules = Arrays.asList(
        new AncestorWithTagRule(),
        new AncestorWithClassRule(),
        new AncestorWithAttributeRule(),
        new AncestorWithAttributeAndValueRule()
    );

    public AncestorResult process(String selector) {
        return rules
            .stream()
            .map(rule -> rule.evaluate(selector))
            .flatMap(optional -> optional.map(Stream::of).orElseGet(Stream::empty))
            .findFirst()
            .orElseThrow(() -> new IllegalArgumentException("Selector does not match any rule"));
    }
}



在這個類中做的第一件事是一個適用於這個領域的所有規則的靜態列表。如果我們有一個新的規則--我們實現一個新的類並將其新增到這個規則列表中。

第二件事是在所有的規則中處理客戶的輸入。它對規則列表進行流式處理,評估每一條規則。規則的第一個非空的結果被返回給客戶端。否則,規則引擎將丟擲一個異常。

關於flatMap()操作的一點。之前的map()函式返回一個Optionals流。然後,flatMap()將一個空的Optionals流轉換為一個空流。否則,就轉換成非空的AncestorResults流,並將其封裝為Optional。該結構與Java 8相容,看起來很冗長。幸運的是,從Java 9開始,這可以被簡化。
 

規則引擎的使用
現在,當我們實現了所有的或規則,規則引擎被定義,讓我們看看如何呼叫和使用這個引擎。

public class ClientSideThatCallsTheRuleEngine {

    public void executeClientCode() {
        // some executions

        AncestorRuleEngine ruleEngine = new AncestorRuleEngine();
        String xpath = ruleEngine.process(selector).getValue();
        
        // other executions
    }
}

就這麼簡單。例項化一個規則引擎。傳入客戶的輸入並得到結果。這很乾淨,簡短,精確。我們隱藏了所有驗證輸入的低階邏輯,建立各自的結果,處理它。與帶有多個if - else分支的直截了當的方法相比。我們新增的邏輯越多,這個if - else怪物就越多。

 

相關文章