Drools 規則語言詳解

chuanzhongdu1發表於2011-07-24

1.  概述:

Drools 3 採用了原生的規則語言,那是一種非 XML 文字格式。在符號方面,這種格式是非常輕量的,並且通過“ expanders ”支援符合你問題域的 Domain Specific Language ( DSL )。這一章把焦點放在了 Drools 原生的規則格式。如果你想從技術上了解規則語言的機制,可以參考“ drl.g ”原始檔,這是用 Antlr3語法來描述規則語言。如果你使用 Rule Workbench ,內容助手將會為你完成大量的規則結構,例如輸入“ ru ”,然後按 ctrl + space ,會為你建立規則結構。

1.1    規則檔案

一個規則檔案通常是一個以 .drl 副檔名結尾的檔案。在一個 drl 檔案中,你可以有多條 rules , functions 等等。儘管如此,你也可以將你的規則分佈在多個檔案中,這有利於管理大量的規則。一個 DRL 檔案是一個簡單的文字檔案。

1.2 規則的結構

一個規則結構大致如下:

rule  " name " 
    ATTRIBUTES
    when
        LHS
    then
        RHS
end

可以看到,這是非常簡單的。通常的標點符號都是不需要的,甚至連“ name ”的雙引號都是不需要的。 ATTRIBUTES 是簡單的,也是可選的,來提示規則的行為方式。 LHS 是規則的條件部分,需要按照一定的語法來寫。 RHS 基本上是一個允許執行 Java 語法的程式碼的塊(以後將會支援 groovy 和 C #)。任何在LHS 中使用的變數都可以在 RHS 中使用。

注意:每行開始的空格是不重要的,除非在 DSL ( Domain Specific Language )語言中有特別的指明。

1.3   Domain Specific Language

Domain Specific Language 是對原生規則語言的加強。它們使用“ expander ”機制。 Expander 機制是一種可擴充套件的 API 。你可以使用 .dsl 檔案,來提供從域或自然語言到規則語言和你的域物件的對映。你可以將 .dsl 檔案看成是對你的域模型的對映。 DSL 提供了更高的規則可讀性,你可以選擇使用你自己建立的 DSL ,或者是原生的規則語言。

1.4 保留字

在規則語言中存在一些保留字。你應該避免使用這些保留字,來命名規則文字中的域物件,屬性,方法,功能。保留字如下: when , then , rule , end ,contains , matches , and , or , modify , retract , assert , salience , function , query , exists , eval , agenda-group , no-loop ,duration , -> , not , auto-focus 。 

2.   註釋

2.1 單行註釋:


2.2 多行註釋:


3.     Package

一個包是 rule 和其他相關結構,像 import 和 global 的集合。 Package 的成員之間通常都是相關聯的。一個 Package 代表了一個名稱空間( namespace),用來使給定的規則組之間保持唯一性。 Package 的名字本身就是名稱空間,並且與檔案或資料夾並無關聯。

可以將來自不同規則源的規則裝配在一起,前提是這些規則必須處在同一個名稱空間中。儘管如此,一個通常的結構是將處於同一個名稱空間中的所有規則都放在同一個相同的檔案中。

下面的 rail-road 圖顯示了組成一個 Package 的所有元件。注意:一個 package 必須有一個名稱空間,並且採用 Java 包名的約定。在一個規則檔案中,各元件出現的位置是任意的,除了“ package ”和“ expander ”語句必須出現在任何一個規則之前,放在檔案的頂部。在任何情況下,分號都是可選的。


3.1 import


Import 語句的使用很像 Java 中的 import 語句。你需要為你要在規則中使用的物件,指定完整的路徑和類名。 Drools 自動從相同命名的 java 包中引入所需的類。

3.2 expander



expander 語句是可選的,是用來指定 Domain Specific Language 的配置(通常是一個 .dsl 檔案)。這使得解析器可以理解用你自己的 DSL 語言所寫的規則。

3.3 global


Global 就是全域性變數。如果多個 package 宣告瞭具有相同識別符號的 global ,那麼它們必需是相同的型別,並且所有的引用都是相同的。它們通常用來返回資料,比如 actions 的日誌,或者為 rules 提供所需的資料或服務。 global 並不是通過 assert 動作放入 WorkingMemory 的,所有當 global 發生改變時,引擎將不會知道。所以, global 不能作為約束條件,除非它們的值是 final 的。將 global 錯誤的使用在約束條件中,會產生令人驚訝的錯誤結果。

注意: global 只是從你的 application 中傳入 WorkingMemory 的物件的命名例項。這意味著你可以傳入任何你想要的物件。你可以傳入一個 service locator ,或者是一個 service 本身。

下面的例子中,有一個 EmailService 的例項。在你呼叫規則引擎的程式碼中,你有一個 EmailService 物件,然後把它放入 WorkingMemory 。在 DRL 檔案中,你宣告瞭一個型別為 EmailService 的 global ,然後將它命名為“ email ”,像這樣: global EmailService email ;。然後在你的規則的 RHS 中,你可以使用它,像這樣: email.sendSMS(number,message) 等等。

4. Function



Function 是將程式碼放到你的規則源中的一種方法。它們只能做類似 Helper 類做的事(實際上編譯器在背後幫你生成了 Helper 類)。在一個 rule 中使用function 的主要優勢是,你可以保持所有的邏輯都在一個地方,並且你可以根據需要來改變 function (這可能是好事也可能是壞事)。 Function 最有用的就是在規則的 RHS 呼叫 actions ,特別是當那個 action 需要反覆呼叫的時候。

一個典型的 function 宣告如下:

function String calcSomething(String arg) {
return   " hola ! " ;
}

注意:“ function ”關鍵字的使用,它並不真正是 Java 的一部分。而 function 的引數就像是一個普通的 method (如果不需要引數就不用寫)。返回型別也跟普通的 method 一樣。在一條規則(在它的 RHS 中,或可能是一個 eval )中呼叫 function ,就像呼叫一個 method 一樣,只需要 function 的名字,並傳給它引數。

function 的替代品,可以使用一個 Helper 類中的靜態方法: Foo.doSomething() ,或者以 global 的方式傳入一個 Helper 類或服務的例項:foo.doSomething() ( foo 是一個命名的 global 變數)。


5. Rule


Rule 結構是最重要的結構。 Rule 使用了形如“ IF ” something “ THEN ” action (當然,我們的關鍵字是“ when ”和“ then ”)的形式。

一個規則在一個 package 中必須要有唯一的名字。如果一個名字中含有空格,那就需要將名字放在雙引號中(最好總是使用雙引號)。

Attribute 是可選的(最好是每行只有一個 Attribute )。

規則的 LHS 跟在“ when ”關鍵字的後面(最好是另起一行),同樣 RHS 要跟在“ then ”關鍵字後面(最好也另起一行)。規則以關鍵字“ end ”結束。規則不能巢狀。

5.1 Left Hand Side

Left Hand Side 其實就是規則的條件部分。 LHS 對應的 rail-road 圖如下,我們在後面會做進一步解釋:




5.2 Right Hand Side

Right Hand Side ( RHS )就是規則的結果( consequence )或者動作( action )部分。 RHS 的目的是 retract 或 add facts 到 WorkingMemory中,還有針對你的 application 的動作。實際上, RHS 是當規則激發( fire )時執行的程式碼塊。

在 RHS 中,你可以使用幾個方便的 method 來改變 WorkingMemory :

“ modify(obj) ”:告訴引擎一個物件已經發生變化,規則必須重新匹配( obj 物件必須是出現在 LHS 中的物件);

“ assert(new Something()) ”:將一個新的 Something 物件加入 WorkingMemory ;

“ assertLogical(new Something()) ”:與 assert 方法類似。但是,當沒有 fact 支援當前激發規則的真實性的時候,這個新物件會自動被 retract ,

“ retract(obj) ”:從 WorkingMemory 中移除一個物件。

這些方法都是巨集指令,提供了到 KnowledgeHelper 例項的快捷方式(參考 KnowledgeHelper 介面)。 KnowledgeHelper 介面可以在 RHS 程式碼塊中呼叫,通過變數“ drools ”。如果你在 assert 進引擎的 JavaBean 中加入“ Property Change Listener ”,在物件發生變化的時候,你就不用呼叫“ modify”方法。

5.3 Rule Attributes



5.3.1 no-loop

預設值: false

型別: boolean

當在 rule 的 RHS 中修改了一個 fact ,這可能引起這個 rule 再次被 activate ,引起遞迴。將 no-loop 設為 true ,就可以防止這個 rule 的 Activation 的再次被建立。

5.3.2 salience

預設值: 0

型別: int

每個 rule 都可以設定一個 salience 整數值,預設為 0 ,可以設為正整數或負整數。 Salience 是優先順序的一種形式。當處於 Activation 佇列中時,擁有高salience 值的 rule 將具有更高的優先順序。

5.3.3 agenda-group

預設值: MAIN

型別: String

Agenda group 允許使用者對 Agenda 進行分組,以提供更多的執行控制。只有具有焦點的組中的 Activation 才會被激發( fire )。

5.3.4 auto-focus

預設值: false

型別: boolean

當一個規則被 activate (即 Activation 被建立了),如果這個 rule 的 auto-focus 值為 true 並且這個 rule 的 agenda-group 沒有焦點,此時這個Activation 會被給予焦點,允許這個 Activation 有 fire 的潛在可能。

5.3.5 activation-group

預設值: N/A

型別: String

當處於同一個 activation-group 中的第一個 Activation fire 後,這個 activation-group 中其餘剩下的 Activation 都不會被 fire 。

5.3.6 duration

預設值:沒有預設值

型別: long

5.4 Column


Example 5.1. Column

Cheese( )
Cheese( type  ==   " stilton " , price  <   10  )

一個 Column 由一個類的一個或多個域約束構成。第一個例子沒有約束,它將匹配 WorkingMemory 中所有的 Cheese 例項。第二個例子對於一個 Cheese 物件有兩個字面約束( Literal Constraints ),它們被用“,”號隔開,意味著“ and ”。



Example 5.2. Bound Column

cheapStilton : Cheese( type  ==   " stilton " , price  <   10  )

這個例子同前一個例子有點類似。但是在這個例子中,我們將一個變數繫結到匹配規則引擎的 Cheese 例項上。這意味著,你可以在另一個條件中使用cheapStilton ,或者在 rule 的 RHS 中。

5.4.1 Field Constraints

Field Constraints 使規則引擎可以從 WorkingMemory 中挑選出合適的 Fact 物件。一個 Fact 的“ Field ”必須符合 JavaBean 規範,提供了訪問 field 的getter 方法。你可以使用 field 的名字直接訪問 field ,或者使用完整的方法名(省略括號)。

例如,以我們的 Chess 類為例,下面是等價的: Cheese(type = = …) 和 Cheese(getType = = …) 。這意味著,你可以使用不太嚴格遵守 JavaBean 規範物件。儘管如此,你要保證 accessor 方法是不帶引數的,以保證它不會改變物件的狀態。

注意:如果一個 field 使用原始型別( primitive type ), Drools 將會把它們自動裝箱成相應的物件(即使你使用 java 1.4 ),但是在 java 1.4 下卻不能自動拆箱。總的來說,儘量在 rule 所使用的類中,使用非原始型別的域。如果是使用 java 5 ,就可以比較隨意了,因為編譯器會幫你執行自動裝拆箱。

5.4.1.1 Operators


有效的操作符是同域型別相關的。例如,對於日期域,“ < ”意味著“之前”。“ matches ”只適用於 String 域,“ contains ”和“ excludes ”只適用於 Collection 型別域。

5.4.1.2 字面值約束( Literal Constraints )

最簡單的域約束就是字面值約束,允許使用者將一個 field 約束於一個已知值。

注意:你可以檢查域是否為 null ,使用 = = 或 != 操作符和字面值‘ null ’關鍵字。如, Cheese(type != null) 。字面值約束,特別是“ = = ”操作符,提供了非常快的執行速度,因為可以使用雜湊法來提高效能。



Numeric 

所有標準的 Java 數字基本型別都可以用。

有效操作符:

·         ==

·         !=

·         >

·         <

·         >=

·         <=

Example 5.3 . Numeric Literal Constraint
Cheese( quantity  ==   5  )

Date

當前只對“ dd-mm-yyyy ”的日期格式提供預設支援。你可以通過指定 drools.dateformat 系統屬性,來改變預設的日期格式。如果需要更多的控制,要用謂詞約束( Predicate Constraint )。

有效操作符:

·         ==

·         !=

·         >

·         <

·         >=

·         <=

Example 5.4. Date Literal Constraint
Cheese( bestBefore  <   " 27-Oct-2007 "  )

String

可以使用任何有效的 Java String 。

有效操作符:

·         ==

·         !=

Example 5.5. String Literal Constraint
Cheese( type  ==   " stilton "  )

Boolean

只能用 “ true ”或“ false ”。 0 和 1 不能被識別,而且 Cheese(smelly) 也是不被允許的。

有效操作符:

·         ==

·         !=

Example 5.6 Boolean Literal Constraint
Cheese( smelly  =   =   true  )

Matches Operator

Matches 操作符後面可以跟任何有效的 Java 正規表示式。

Example 5.7. Regular Expression Constraint
Cheese( type matches  " (Buffulo)?\\S*Mozerella "  )

Contains Operator and Excludes Operator

“ contains ”和“ excludes ”可以用來檢查一個 Collection 域是否含有一個物件。

Example 5.8. Literal Cosntraints with Collections
CheeseCounter( cheeses contains  " stilton "  )
CheeseCounter( cheeses excludes  " chedder "  )

5.4.1.3 Bound Variable Constraint

可以將 Facts 和它們的 Fields 附給一個 Bound Variable ,然後在後續的 Field Constraints 中使用這些 Bound Variable 。一個 Bound Variable 被稱為宣告( Declaration )。 Declaration 並不能和“ matches ”操作符合用,但卻可以和“ contains ”操作符合用。

Example 5.9. Bound Field using '==' operator
Person( likes : favouriteCheese )
Cheese( type  ==  likes )

在上面的例子中,“ likes ”就是我們的 Bound Variable ,即 Declaration 。它被繫結到了任何正在匹配的 Person 例項的 favouriteCheese 域上,並且用來在下一個 Column 中約束 Cheese 的 type 域。可以使用所有有效的 Java 變數名,包括字元“ $ ”。“ $ ”經常可以幫助你區分 Declaration 和 field 。下面的例子將一個 Declaration 繫結到匹配的例項上,並且使用了“ contains ”操作符。注意: Declaratino 的第一個字元用了“ $ ”:

Example 5.10 Bound Fact using 'contains' operator

$stilton : Cheese( type  ==   " stilton "  )
Cheesery( cheeses contains $stilton )

5.4.1.4 Predicate Constraints


Predicate 表示式可以使用任何有效的 Java 邏輯表示式。先前的 Bound Declaration 可以用在表示式中。

下面的例子將會找出所有男性比女性大 2 歲的 pairs of male/femal people :

Example 5.11. Predicate Constraints
Person( girlAge : age, sex  =   =   " F "  )
Person( boyAge : age  ->  ( girlAge.intValue()  +   2   ==  boyAge.intValue() ), sex  =   =   " M "  )

5.4.1.5 Return Value Constraints

一個 Retrurn Value 表示式可以使用任何有效的 Java 表示式,只要它返回一個物件,不能返回原始資料型別。如果返回值是原始資料型別,要先進行裝箱。先前的 Bound Declaration 也可以使用在表示式中。

下面的例子跟上一節的例子一樣,也將會找出所有男性比女性大 2 歲的 pairs of male/femal people 。注意:這裡我們不用繫結 boyAge ,增加了可讀性:

Example 5.12. Return Value Constraints 
Person( girlAge : age, sex  =   =   " F "  )
Person( age  =   =  (  new  Integer(girlAge.intValue()  +   2 ) ), sex  =   =   " M "  )

5.5 Conditional Elements

Conditional Elements 用來連線一個或多個 Columns 。

5.5.1 “ and ”


Example 5.13.  And 

Cheese( cheeseType : type )  &&  Person( favouriteCheese  ==  cheeseType )
Cheese( cheeseType : type ) and Person( favouriteCheese 
 ==  cheeseType )

5.5.2  or 


Example 5.14. or 

Person( sex  ==   " f " , age  >   60  )  ||  Person( sex  ==   " m " , age  >   65  )
Person( sex 
 ==   " f " , age  >   60  ) or Person( sex  ==   " m " , age  >   65  )



Example 5.15. or with binding 

pensioner : Person( sex  ==   " f " , age  >   60  )  ||  pensioner : Person( sex  ==   " m " , age  >   65  ) 
pensioner : ( Person( sex  ==   " f " , age  >   60  ) or Person( sex  ==   " m " , age  >   65  ) )

“ or ” Conditional Element 的使用會導致多條 rule 的產生,稱為 sub rules 。上面的例子將在內部產生兩條規則。這兩條規則會在 WorkingMemory 中各自獨立的工作,也就是它們都能進行 match , activate 和 fire 。當對一個“ or ” Conditional Element 使用變數繫結時,要特別小心,錯誤的使用將產生完全不可預期的結果。

可以將“ OR ” Conditional Element 理解成產生兩條規則的快捷方式。因此可以很容易理解,當“ OR ” Conditional Element 兩邊都為真時,這樣的一條規則將可能產生多個 activation 。

5.5.3 “ eval ”


Eval is essentially a catch all which allows any semantic code (that returns a primitive boolean) to be executed. 在表示式中可以引用在 LHS 中出現的變數,和在 rule package 中的 Functions 。一個 Eval 應該是 LHS 中的最後一個 Conditional Element 。在一個 rule 中,你可以有多個 eval 。

Eval 不能被索引,因此不能像 Field Constraints 那樣被優化。儘管如此,當 Functions 的返回值一直在變化時,應該使用 Eval ,因為這在 Field Constraints 中時不允許的。如果規則中的其他條件都匹配,一個 eval 每次都要被檢查。(現在還不理解到底 eval 要怎麼用?)

Example 5.16. eval 

p1 : Parameter() 
p2 : Parameter()
eval( p1.getList().containsKey(p2.getItem()) )
eval( isValid(p1, p2) )  // this is how you call a function in the LHS - a function called  // "isValid" 

5.5.4 “ not ”


 not ”是一階邏輯的存在量詞( first order logic’s Existential Quantifier ) , 用來檢查 WorkingMemory 中某物件的非存在性。現在,只有 Columns可以放在 not 中,但是將來的版本會支援“ and ”和“ or ”。

Example 5.17. No Buses 

not Bus()

Example 5.18. No red Buses 

not Bus(color  ==   " red " )
not ( Bus(color  ==   " red " , number  =   =   42 ) )  // brackets are optional 

5.5.5 “ exists ”


“ exists ” 是一階邏輯的存在量詞( first order logic’s Existential Quantifier ),用來檢查 WorkingMemory 中某物件的存在性。可以將“ exists ”理解為“至少有一個”( at least one… )。它的意義不同於只有 Column 本身,“ Column ”本身可以理解為“對於每一個 … ”( for each of … )。如果你對一個 Column 使用了“ exists ”,那麼規則將只 activate 一次,而不管 WorkingMeomry 中有多少個資料匹配了那個條件。

現在,只有 Columns 可以放在“ exists ”中,但是將來的版本會支援“ and ”和“ or ”。

Example 5.19. At least one Bus 

exists Bus()

Example 5.20. At least one red Bus 

exists Bus(color  ==   " red " )

5.5.6 “ group ”


Group 的作用相當於代數學中的“()”,顯式的指明操作的順序。

5.6 再談自動裝箱和原始型別

Java 5 支援在原始型別與其對應包裝類之間的裝拆箱。儘管如此,因為要讓 drools 能夠在 J2SE 1.4 下執行,我們不能依靠 J2SE 。因此, drools 自己實現了自動裝箱。被引用(即被 Bound Variable 繫結)的 Field 將被自動進行裝箱(如果它們本身就是 object ,就不會有任何變化)。儘管如此,必須要注意的是,他們並不會被自動拆箱。

還有一點要注意的,就是對於 ReturnValue Constraints ,返回值的程式碼必須返回一個物件,而不能是一個原始型別。

6 . Query



一個 query 只包含了一個 rule 的 LHS 結構(你不用指定“ when ”或“ then ”)。這是查詢 WorkingMemory 中匹配條件的 Facts 的簡單的方法。

要得到結果,要使用 WorkingMemory.getQueryResults(“name”) 方法,其中“ name ”就是 query 的名字。 Query 的名字在 RuleBase 中是全域性的,所以, do not add queries of the same name to different packages for the same RuleBase 。

下面的例子建立了一個簡單的 query 來查詢所有年齡大於 30 的人:

Example 6.1. Query People over the age of 30
 
query "people over the age of 30" 
    person : Person( age > 30 )
end

我們通過一個標準的迴圈來迭代一個返回的QueryResults物件。每一次的iterate將返回一個QueryResult物件。我們可以用QueryResult物件的get()方法來訪問每個Column,通過傳入Bound Declaration或index position。

Example 6.2. Query People over the age of 30

QueryResults results = workingMemory.getQueryResults( "people over the age of 30" );
System.out.println( "we have " + results.size() + " people over the age  of 30" );

System.out.println( "These people are are over 30:" );

for ( Iterator it = results.iterator; it.hasNext(); ) {
    QueryResult result = ( QueryResult ) it.next();
    Person person = ( Person ) result.get( "person" );
    System.out.println( person.getName() + "\n" );
}

原文地址:http://www.blogjava.net/guangnian0412/archive/2006/06/09/51756.html


相關文章