drools中then部分的寫法

huan1993發表於2022-05-25

1、背景

此處介紹一下droolsthen部分的寫法,以及一些內建的方法,比如insert/delete/modify等等。同時也介紹一下rule的繼承,和在when中實現if else if 等操作。

2、支援的方法

drools提供了一些內建的方法,這些方法會修改drools的工作記憶體Fact物件的值。從而會導致重新進行模式匹配。

2.1 insert 插入物件到工作記憶體中

insert是向工作記憶體中插入物件,同時會導致重新進行規則的模式匹配。同時當規則不滿足時,不會自動刪除。

2.1.1 需求

當發生火災Fire時,向工作記憶體中插入一個Alarm物件,告警發生後,刪除工作記憶體中的Fire物件,然後檢測Alarm物件是否還存在。

2.1.2 drl檔案編寫

package rules

import com.huan.drools.insertmethod.Fire
import com.huan.drools.insertmethod.Alarm

rule "insert_發生火災時,往工作記憶體中插入告警物件"
    when
        $fire: Fire()
    then
        System.out.println("1、發生火災時,往工作記憶體中插入告警物件");
        insert(new Alarm($fire));
end

rule "insert_當規則記憶體中存在告警物件,進行告警,然後刪除火災物件"
    when
        $fire: Fire()
        $alarm: Alarm( fire == $fire )
    then
        System.out.println("2、進行告警,然後刪除對應的火災物件");
end

rule "insert_檢測告警物件是否還是存在-01"
    when
        Alarm()
    then
        System.out.println("3、insert 插入的告警物件還存在");
        // 刪除工作記憶體中的Fire物件
        delete($fire);
end

rule "insert_檢測告警物件不存在"
    when
        not Alarm()
    then
        System.out.println("3、insert 插入的告警物件不存在");
end

此處使用的是 insert進行插入

2.1.3 部分java程式碼編寫

// 將火災物件插入到工作記憶體中
kieSession.insert(new Fire());
// 只觸發規則名稱以 insert_ 開頭的規則
kieSession.fireAllRules(new RuleNameStartsWithAgendaFilter("insert_"));

2.1.4 執行結果

1、發生火災時,往工作記憶體中插入告警物件
2、進行告警,然後刪除對應的火災物件
3、insert 插入的告警物件還存在

2.1.5 結論

  1. insert可以向工作記憶體中插入Fact物件。
  2. insert方法呼叫後,會導致模式的重新匹配,導致之前不會執行的規則,重新執行。
  3. insert方法插入到工作記憶體的物件,在規則不成立時,不會自動刪除,需要手動刪除,注意和insertLogical的區別

2.2 insertLogical 插入物件到工作記憶體中

insert是向工作記憶體中插入物件,同時會導致重新進行規則的模式匹配。同時當規則不滿足時,會自動刪除。

2.2.1 需求

當發生火災Fire時,向工作記憶體中插入一個Alarm物件,告警發生後,刪除工作記憶體中的Fire物件,然後檢測Alarm物件是否還存在。

2.2.2 drl檔案編寫

package rules

import com.huan.drools.Fire
import com.huan.drools.Alarm

rule "insertLogical_發生火災時,往工作記憶體中插入告警物件"
    when
        $fire: Fire()
    then
        System.out.println("1、發生火災時,往工作記憶體中插入告警物件");
        insertLogical(new Alarm($fire));
end

rule "insertLogical_當規則記憶體中存在告警物件,進行告警,然後刪除火災物件"
    when
        $fire: Fire()
        $alarm: Alarm( fire == $fire )
    then
        System.out.println("2、進行告警,然後刪除對應的火災物件");
        delete($fire);
end

rule "insertLogical_檢測告警物件是否還是存在-01"
    when
        Alarm()
    then
        System.out.println("3、insertLogical 插入的告警物件還存在");
end

rule "insertLogical_檢測告警物件不存在"
    when
        not Alarm()
    then
        System.out.println("3、insertLogical 插入的告警物件不存在");
end

此處使用的是insertLogical插入

2.2.3 部分java程式碼編寫

kieSession.insert(new Fire());
kieSession.fireAllRules(new RuleNameStartsWithAgendaFilter("insertLogical_"));

2.2.4 執行結果

1、發生火災時,往工作記憶體中插入告警物件
2、進行告警,然後刪除對應的火災物件
3、insertLogical 插入的告警物件不存在

2.2.5 結論

  1. insertLogical可以向工作記憶體中插入Fact物件。
  2. insertLogical方法呼叫後,會導致模式的重新匹配,導致之前不會執行的規則,重新執行。
  3. insertLogical方法插入到工作記憶體的物件,在規則不成立時,會自動刪除注意和insert的區別

2.3、update 更新工作記憶體中的物件

update: 使用它來指定要更新的欄位和整個相關Fact,並將更改通知 Drools 引擎。 Fact發生更改後,您必須在更改可能受更新值影響的另一個事實之前呼叫 update。 為避免此新增步驟,請改用 modify 方法。

2.3.1 需求

規則一: 當工作記憶體中存在火災物件Fire,並且名字name為空時觸發規則,同時在設定火災的名字為大火災
規則二: 當火災存在名字時,輸出火災名字

2.3.2 drl 檔案編寫

package rules

import com.huan.drools.Fire
import com.huan.drools.Alarm

rule "update_當存在火災物件時,設定一個火災的名字"
    when
        $fire: Fire(name == null)
    then
        System.out.println("1、設定火災名字");
        $fire.setName("大火災");
        update($fire)
end

rule "update_當火災物件存在名字時觸發"
    when
        $fire: Fire(name != null)
    then
        System.out.println("2、火災物件的名字為: " + $fire.getName());
end

2.3.3 部分java檔案編寫

kieSession.insert(new Fire());
kieSession.fireAllRules(new RuleNameStartsWithAgendaFilter("update_"));

2.3.4 執行結果

1、設定火災名字
2、火災物件的名字為: 大火災

2.3.4 結論

  1. update會導致模式的重新匹配。
  2. update會修改工作物件記憶體中的值。

2.4、modify 更新工作記憶體中的物件

modify: 使用它來指定要為Fact物件修改的欄位並將更改通知 Drools 引擎。 此方法提供了一種結構化的事實更新方法。 它將更新操作與 setter 呼叫相結合以更改物件欄位。

2.4.1 需求

規則一: 當工作記憶體中存在火災物件Fire,並且名字name為空時觸發規則,同時在設定火災的名字為大火災
規則二: 當火災存在名字時,輸出火災名字

2.4.2 drl 檔案編寫

package rules

import com.huan.drools.Fire
import com.huan.drools.Alarm

rule "modify_當存在火災物件時,設定一個火災的名字"
    when
        $fire: Fire(name == null)
    then
        System.out.println("1、設定火災名字");
        modify($fire){
            setName("大火災")
        }
end

rule "modify_當火災物件存在名字時觸發"
    when
        $fire: Fire(name != null)
    then
        System.out.println("2、火災物件的名字為: " + $fire.getName());
end

2.4.3 部分java檔案編寫

kieSession.insert(new Fire());
kieSession.fireAllRules(new RuleNameStartsWithAgendaFilter("modify_"));

2.4.4 執行結果

1、設定火災名字
2、火災物件的名字為: 大火災

2.4.5 結論

  1. modify會導致模式的重新匹配。
  2. modify會修改工作物件記憶體中的值。
  3. 一般情況下使用modify,不要使用update

2.5 delete 刪除工作記憶體中的物件

用法:delete(<object>)

retract也是和delete一樣的效果,但是推薦使用delete

3、drools變數的簡單使用

package rules

rule "drools_變數的使用"
    when
        eval(true)
    then
        System.out.println("Match啟用的當前觸發規則: " + drools.getMatch());
        System.out.println("當前觸發規則的名字: " + drools.getRule().getName());
        // System.out.println("終止規則執行fireUntilHalt(): " + drools.getKieRuntime().halt());
        // System.out.println("啟用AgendaGroup組: " + drools.getKieRuntime().getAgenda().getAgendaGroup( "CleanUp" ).setFocus());

        System.out.println("獲取所有全域性變數: " + drools.getKieRuntime().getGlobals());
        // System.out.println("設定全域性變數:" +  drools.getKieRuntime().setGlobal("username","huan"); );
        // System.out.println("獲取查詢結果:" + drools.getKieRuntime().getQueryResults());
end

4、規則的繼承

4.1 需求

規則一: 如果使用者(customer)的年齡(age)大於60歲,則打0.9折。
規則二: 在規則一的基礎上,如果使用者有車(car),則可以免費停車(freeParking)。

4.2 drl檔案編寫

package rules

import com.huan.drools.Customer
import com.huan.drools.Car

rule "rule_extends_規則一"
    when
        $c: Customer(age > 60)
    then
        modify($c){
            setDiscount(0.9)
        }
        System.out.println("觸發規則一:使用者年齡>60歲,打0.9折");
end

// 規則二繼承規則一的條件
rule "rule_extends_規則二" extends "rule_extends_規則一"
    when
        $car: Car()
    then
        modify($car){
            setFreeParking(true)
        }
        System.out.println("觸發規則二:使用者有車,免費停車");
end

此處rule_extends_規則二繼承了rule_extends_規則一,所以規則一的條件也繼承了。

4.3 部分java程式碼

Car car = new Car();
Customer customer = new Customer();
customer.setAge(65);
kieSession.insert(customer);
kieSession.insert(car);
kieSession.fireAllRules(new RuleNameStartsWithAgendaFilter("rule_extends_"));

客戶有車,並且年齡是65歲,滿足上方的規則一和規則二

4.4 執行結果

觸發規則一:使用者年齡>60歲,打0.9折
觸發規則二:使用者有車,免費停車

4.5 結論

可以看到在rule上使用extends關鍵字,可以實現規則的繼承。

5、使用do[...]語法重寫上方繼承的例子

5.1 需求

只要使用者大於60歲,直接打0.9折,如果還有車,則可以免費停車。

5.2 drl檔案編寫

package rules

import com.huan.drools.Customer
import com.huan.drools.Car

rule "命名結果_rule"
    when
        $c: Customer(age > 60)
        do[giveDiscount] // 當上方的條件成立時執行 then [giveDiscount]
        $car: Car() // 此條件成立時,執行預設的 then
    then
        modify($car){
            setFreeParking(true)
        };
        System.out.println("使用者有車,免費停車");
    then [giveDiscount]
        modify($c){
            setDiscount(0.9)
        };
        System.out.println("使用者年齡>60歲,打0.9折");
end

解釋: 見上方的規則檔案裡的注視

5.3 部分java程式碼編寫

Car car = new Car();
Customer customer = new Customer();
customer.setAge(65);
kieSession.insert(customer);
kieSession.insert(car);
kieSession.fireAllRules(new RuleNameStartsWithAgendaFilter("命名結果_"));

5.4 執行結果

使用者年齡>60歲,打0.9折
使用者有車,免費停車

也實現了需求

5.5 結論

通過when中使用 do[名字] 然後 then then 名字 也可以實現。

6、實現 if else if 的效果

6.1 需求

完成類似 if else if else效果。見下方的各種執行結果

6.2 規則檔案

rule "if else-if"
    when
       $customer: Customer(age > 60) // 規則記憶體中存在Customer物件,並且age>60
        if($customer.getLevel() == 1) do[level1] // 使用者的級別是1,執行then[level1],然後繼續執行下方的條件
        else if ($customer.getLevel() == 2) break[level2] // 使用者的級別是2,執行then[level2],不在執行下方的條件
        else do[levelOther] // 其他的level級別,執行then[levelOther],然後在執行下方的條件
        Car()
    then
        System.out.println("我執行了");
    then[level1]
        System.out.println("level1");
    then[level2]
        System.out.println("level2");
    then[levelOther]
        System.out.println("levelOther");
end

6.3 執行結果圖

6.4 各種執行結果-也就是上圖的解釋

1、Customerage小於60
輸出: 沒有輸出。
2、Customerage大於60並且level=1,沒有Car
輸出: level1
3、Customerage大於60並且level=1,有Car
輸出: level1 我執行了
4、Customerage大於60並且level=2,沒有Car
輸出: level2
5、Customerage大於60並且level=2,有Car
輸出: level2
6、Customerage大於60並且level=3,沒有Car
輸出: levelOther
7、Customerage大於60並且level=3,有Car
輸出: levelOther 我執行了

6.5 do和 break 有什麼區別

do:執行完之後,還會繼續判斷後面的執行條件。 (即還會執行後面的Car判斷,根據是否有Car獲取不同的結果
break:執行完之後,不會在判斷後面的執行條件。(即忽略了後面的Car判斷,rule執行完了

7、完整程式碼

https://gitee.com/huan1993/spring-cloud-parent/tree/master/drools/drools-drl-then

8、參考文件

1、https://docs.drools.org/7.69.0.Final/drools-docs/html_single/index.html#drl-rules-THEN-con_drl-rules

相關文章