规则引擎与Drools

1 简介

1.1 规则

  • 每条规则都是一组条件决定的一系列结果
  • 一条规则可能与其他规则共同决定最终结果
  • 可能存在条件互相交叉的规则,此时有必要规定规则的优先级

规则作为一种知识,其典型运用就是通过实际情况,根据给定的一组规则,得出结论。这个结论可能是某种静态的结果,也可能是需要进行的一组操作。

规则语言可以分为结构化的(Structured)和基于标记的(Markup,通常为xml)。

常见的结构化的规则描述语言:

  • srl(Structured Rule Language)
  • drl(Drools Rule Language)

常见的基于标记的规则描述语言:

  • RuleML(Rule Markup Language)
  • SRML(Simple Rule Markup Language)
  • BRML(Business Rules Markup Language)
  • SWRL(A Semantic Web Rule Language)

1.2 推理机

运用过程叫做推理。如果由程序来处理推理过程,那么这个程序就叫做推理机/推理引擎(Inference Engine)。推理机是专家系统(专家系统是人工智能的一个分支)的核心模块。

推理引擎根据知识表示的不同采取的控制策略也是不同的,常见的类型包括基于神经网络、基于案例和基于规则的推理机。

1.3 规则引擎

基于规则的推理机易于理解、易于获取、易于管理,被广泛采用。这种推理引擎被称为“规则引擎”。

在规则引擎中,将知识表达为规则(rules),要分析的情况定义为事实(facts)。二者在内存中的存储分别称为Production Memory和Working Memory。

  • rules和facts:规则引擎接受的输入参数
  • Pattern Matcher:根据facts找到匹配的rules
    • 正向推理(Forward-Chaining):正向推理也叫演绎法,由事实驱动,从 一个初始的事实出发,不断地应用规则得出结论。首先在候选队列中选择一条规则作为启用规则进行推理,记录其结论作为下一步推理时的证据。如此重复这个过程,直到再无可用规则可被选用或者求得了所要求的解为止。
    • 反向推理(Backward-Chaining):反向推理也叫归纳法,由目标驱动,首先提出某个假设,然后寻找支持该假设的证据,若所需的证据都能找到,说明原假设是正确的;若无论如何都找不到所需要的证据,则说明原假设不成立,此时需要另做新的假设。
  • Agenda:管理PatternMatcher挑选出来的规则的执行次序
  • Execution Engine:在外围负责根据Agenda输出的rules执行具体的操作

1.4 规则引擎的作用

规则引擎可以将规则的定义从代码中分离出来,将推理过程封装到规则引擎内部进行处理,这带来几个好处:

  • 规则外部化,即有利于规则知识的复用,也可避免改变规则时带来的代码变更问题
  • 由规则引擎使用某种算法进行推理过程,不需要编写复杂晦涩的逻辑判断代码
  • 开发人员的不需要过多关注逻辑判断,可以专注于逻辑处理

2 Drools

DROOLS - SAMPLE DROOLS PROGRAM

2.1 概览

完整的drl文件包含以下几个部分:package,import,declares,globals,functions,queries,rules。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.sample
import com.sample.DroolsTest.Message;
rule "Hello World"
when
m : Message(status == Message.HELLO, myMessage : message)
then
System.out.println(myMessage);
m.setMessage("Goodbye cruel world");
m.setStatus(Message.GOODBYE);
update(m);
end
rule "GoodBye"
when
Message(status==Message.GOODBYE, myMessage:message)
then
System.out.println(myMessage);
end

2.2 package和import

与Java类似,drools头部必须要有一个package声明和import声明,且package声明必须放在规则文件的第一行。

2.3 规则定义

一个规则通常包含三个部分:attribute(属性),LHS(条件部分),RHS(结果部分)。

1
2
3
4
5
6
7
rule "name"
attributes
when
LHS
then
RHS
end

2.3.1 LHS(条件部分)

  • 可以包含多个条件,若条件部分为空的话,引擎自动添加一个eval(true)的条件。
  • 多个条件之间可使用and或or连接,默认是and关系。
  • 条件的语法:[绑定变量名:]\Object([field约束])
    • 绑定变量名:可在该LHS后续的条件中引用
    • field约束:当前对象里相关字段的条件限制,多个约束之间用”&&”(and),”||”(or)和”,”(and)连接。约束中可使用的比较操作符包括:>, >=, <, <=, ==, !=, contains, not contains, memberof, not memberof, matches, not matchers
1
2
3
4
5
6
7
8
9
10
11
12
when
order:Order();
then
...
when
$room : Room( ) #变量名可以是$开头
$sprinkler : Sprinkler( room == $room, on == true )
not Fire( room == $room )
then
modify( $sprinkler ) { setOn( false ) };
System.out.println( "Turn off the sprinkler for room " + $room.getName() );

2.3.2 RHS(结果部分)

  • RHS部分定义了当LHS满足时要进行的操作
  • RHS中可以编写代码,可以使用LHS部分中定义的绑定变量名以及drl头部定义的全局变量。
  • 虽然可以在RHS中直接写java代码,但不建议代码中有条件判断,否则就违背了使用规则的初衷。
2.3.2.1 使用宏函数

RHS中可以使用宏函数对工作空间进行操作。当调用宏函数后,所有为设置“no-loop”属性的规则都会呗重新分配,符合条件的重新触发。

宏函数都是StatefulSession中定义的方法,包括:

  • insert:将一个Fact对象插入到当前的Working Memory
  • update:对当前Working Memory中的Fact进行更新
  • retract:从Working Memory中删除某个Fact对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
when
...
then
Customer cus=new Customer();
cus.setName("张三");
insert(cus);
when
$customer:Customer(name=="张三", age<10)
then
$customer.setAge(customer.getAge()+1);
update(customer);
when
$customer:Customer(name=="张三",age<10)
then
Customer customer=new Customer();
customer.setName("张三");
customer.setAge($customer.getAge()+1);
# 用新对象替换Working Memory中的旧对象
update(drools.getWorkingMemory().getFactHandleByIdentity($customer),customer);
when
$customer:Customer(name=="张三")
then
retract($customer);
2.3.2.2 modify代码块

modify代码块用于快速修改并更新(update)某个 Fact 对象的多个属性。

1
2
3
4
5
6
7
8
9
10
11
12
modify(fact-expression){
<修改 Fact 属性的表达式>[,<修改 Fact 属性的表达式>*]
}
when
$customer:Customer(name=="张三",age==20);
then
modify($customer){
setId("super man"),
setAge(30)
}
2.3.2.3 drools宏对象

通过使用drools宏对象可以实现在规则文件里直接访问 Working Memory,从而获取对当前的 Working Memory的更多控制。

drools宏对象的常用方法包括:

  • drools.getWorkingMemory():获取当前的 WorkingMemory对象
  • drools.halt():在当前规则执行完成后,不再执行 其它未执行的规则
  • drools.getRule():得到当前的规则对象
  • drools.insert(new Object):向当前的WorkingMemory 当中插入指定的对象,功能与宏函数insert相同
  • drools.update(new Object):更新当前的WorkingMemory中指定的对象,功能与宏函数update相同
  • drools.update(FactHandle Object):更新当前的WorkingMemory中指定的对象,功能与宏函数update相同
  • drools.retract(new Object):从当前的WorkingMemory中删除指 定的对象,功能与宏函数retract相同
2.3.2.4 kcontext宏对象

用来得到当前的KnowledgeRuntime对象,KnowledgeRuntime对象可以实现与引擎的各种交互。

2.3.3 规则属性

  • salience:设置规则执行的优先级,默认值为0。数字越大越先执行,可以设置为负数,数字相同的使用随机顺序。设置该属性salience 10.
  • no-loop:默认为false。设置为true时,表示该规则只会被引擎检查一次。引擎内部对Fact更新时,忽略本规则的再次检查。
  • date-effective:设置规则的开始生效日期,默认接受“dd-MMM-yyyy”格式的字符串。修改日期格式:System.setProperty("drools.dateformat","yyyy-MM-dd")
  • data-expires:设置规则的过期日期,默认接受“dd-MMM-yyyy”格式的字符串。修改日期格式:System.setProperty("drools.dateformat","yyyy-MM-dd")
  • enabled:设置规则是否可用,默认为true。
  • dialect:设置规则中使用的编程语言,默认为java,可设置为mvel。获取该属性的设置:drools.getRule().getDialect(),设置该属性dialect mvel
  • duration:指定延迟时间,在另一个线程中触发规则,单位为毫秒。
  • activation-group:为规则划指定一个活动组(组名为字符串)。同一个活动组中的规则只执行一个,根据优先级(salience)来决定执行哪一个规则。
  • agenda-group:为规则指定一个议程(agenda)组。指定了议程组的规则只有在该议程组得到焦点时才被触发。
  • auto-focus:指定了auto-focus属性为true,则该规则自动得到焦点。
  • ruleflow-group:指定规则流组。
  • lock-on-active:当在规则上使用ruleflow-group属性或agenda-group属性的时候,将lock-on-action属性 的值设置为 true,可避免因某些 Fact 对象被修改而使已经执行过的规则再次被激活执行。
  • when

2.4 注释

  • 多行注释:/*注释*/
  • 单行注视:#注释//注释

2.5 类型声明

todo

2.6 全局变量

todo

2.7 函数和import function

2.7.1 函数的定义和使用

函数是定义在规则文件中的一段代码,用于将规则文件中会被若干个规则复用的业务操作封装起来,减少规则编写的工作量。函数的可见范围是规则函数所在的规则文件。

函数以function定义。可以是void,也可以有返回值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package test
import java.util.List;
import java.util.ArrayList;
function void setOrder(Customer customer, int orderSize) {
List ls = new ArrayList();
for (int i=0; i<orderSize; i++){
Order order=new Order();
ls.add(order);
}
customer.setOrder(ls);
}
rule "rule1"
when
$customer:Customer()
then
setOrder($customer,5);
System.out.println("rule 1 customer has order size:"+$customer.getOrders().size());
end
rule "rule2"
when
$customer :Customer()
then
setOrder($customer,10);
System.out.println("rule 2 customer has order size:"+$customer.getOrders().size());
end

2.7.2 引入静态方法

实际应用当中,可以考虑使用在java类当中定义静态方法的办法来替代在规则文件当中定义函数。

Drools 提供了一个特殊的 import 语句:import function。通过该 import 语句,可以实现将一个java类中静态方法引入到一个规则文件当中,使得该文件当中的规则可以像使用普通的Drools函数一样来使用java类中某个静态方法。

2.8 查询定义

查询用于根据条件在当前的 WorkingMemory 当中查找 Fact。

Drools 当中查询可分为两种:

  • 不需要外部传入参数
  • 需要外部传入参数
1
2
3
4
5
6
7
query "testQuery"
customer:Customer(age>30,orders.size >10)
end
query "testQuery2"(int $age,String $gender)
customer:Customer(age>$age,gender==$gender)
end