Chapter 4
主要讨论的问题
- 规则文件读取和修改数据的几种方式
- 用属性,global变量以及一些其他特性来配置规则
- 讨论drools规则的执行怎样被其他其他机制来控制
用global变量添加外部交互
chapter-04-kjar 中ruleAttributes/classify-item-rules.drl
修改working memory中的数据
- 拆更小的规则
- 我们不能在一个规则中调用另一个规则,所以只能修改或添加数据来使其他规则重新计算它们,看是否可以触发条件。
insert
rule "Classify Customer - Gold"
when
$c: Customer( category == Customer.Category.GOLD )
then
insert(new IsGoldCustomer($c));
end
rule "Classify Item - Low price"
when
$i: Item(cost < 10.00)
then
insert(new IsLowRangeItem($i));
end
rule "Suggest gift"
when
IsGoldCustomer($c: customer)
IsLowRangeItem($i: item)
then
System.out.println("Suggest giving a gift of item "+$i.
getName()+" to customer +"$c.getName());
end
modify 和 update
rule "Categorize Customer - Gold"
when
$c: Customer( category == Customer.Category.NA )
$o: Order(customer == $c, orderLines.size() > 10)
then
modify($c){setCategory(Customer.Category.GOLD);}
end
规则修改了Customer对象,这个修改之后,规则引擎将会接收到这个Customer对象需要重新计算,所有规则都生效。依赖customer这个对象的规则都会重新被触发。
update与modify类似,唯一不同的是写法,上面的modify代码块可以用下面的update来替代
$c.setCategory(Customer.Category.GOLD);
update($c);
推荐使用modify,因为modify允许 syntactically group the fact's execution that should trigger rule re-evaluations
delete/retract
rule "Init current date"
when
then insert(new Date());
end
rule "Expire coupons"
when
$now: Date()
$cp: Coupon( validUntil before $now )
then
delete($cp);
end
rule "Execute coupon"
when
$o: Order()
$cp: Coupon(order == $o)
then
System.out.println("We have a coupon for this order!");
end
Rule dates management
1、在某个时间段内:
date-effective "01-Jan-2015"
date-expires "31-Dec-2020"
这两个属性允许你控制规则在一个特定的时间范围内生效,指定了生效的起始时间和结束时间,
rule "Add special tax of 3%"
date-effective "01-Jan-2015"
date-expires "31-Dec-2020"
when $i: Item()
then $i.setSalePrice($i.getSalePrice() * 1.03);
end
2、周期性的时间段内:
calendars "weekdays"
timer (int:0 1h)
只要是工作日,每间隔1小时(第一次不需要1小时,立即执行)匹配一次条件。此外,需要通过kiesession来设置calendar,
ksession.getCalendars().set("weekday", new Calendar() {
//for simplicity, any date/time matches this calendar
public void isTimeIncluded(long timestamp) {
return true;
}
});
Controlling loops in rules
no-loop
The no-loop rule attribute prevents a rule from reactivating itself, irrespective of the changes the rule makes to the working memory
但是需要注意:
If another rule changes the working memory in a way that matches this rule again, the no-loop condition won't prevent it from executing a second time.
就是说,加上该属性后,如果该规则本身对working memory做的修改重新匹配了该规则,那么将不再执行该规则。
rule "Apply 10% discount on notepads BUT ONLY ONCE"
no-loop true
when $i: Item(name == "notepad", $sp: salePrice)
then modify($i) { setSalePrice($sp * 0.9); }
end
true是可选的,可以通过上下文来确定该值是true还是false。
Lock-on-active
no-loop可以阻止自身的循环调用,但无法阻止规则之间的循环调用,Lock-on-active就是干这个事儿的。换句话说,加上这个属性后,相同对象的改变不会再重新触发规则的执行。
rule "Give extra 2% discount for orders larger than 15 items"
no-loop true
when $o: Order(totalItems > 15)
then modify ($o) { increaseDiscount(0.02); }
end
rule "Give extra 2% discount for orders larger than $100"
no-loop true
when $o: Order(total > 100.00)
then modify ($o) { increaseDiscount(0.02); }
end
Lock-on-active 是no-loop的加强版,
Only when the ruleflow-group is no longer active or the agenda group loses the focus; these rules, with lock-on-active set to true, become eligible again for matching on the same objects to be possible again.
Model properties execution control
给对象增加一个标志位来确定是否重新匹配规则
rule "Add 2% discount for orders larger than $100"
when $o:
Order(total > 100.00, has100DollarsDiscount == false)
then
modify($o){
increaseDiscount(0.02);
setHas100DollarsDiscount(true);
}
end
这种解决办法有两个问题:
- 对象始终需要维护一个和本身内容无关的变量flag,唯一的用途和规则的执行有关。
- 规则如果很多,那么flag将会变得很难管理与可读。
写规则应该始终坚持:
- 尽量保持规则的独立性
尽量保持规则的简单,原子性
Declared types
定义数据模型不仅仅可以使用java类来实现,也可以在规则文件中定义
declare SpecialOrder extends Order
whatsSoSpecialAboutIt: String order: Order applicableDiscount: Discount
end
需要说明:
- 这种在规则文件中定义的类型可以用在规则文件的条件(conditions)和结果(consequences)中
- 可以访问你所声明的所有属性的getter/setter 方法,而不用定义他们,规则引擎负责创建他们
规则文件中定义的类也可以在规则文件外部访问,但是需要用到反射,所以最好只在规则文件中应用。如:
KieSession ksession = ...; //previously initialized FactType type = ksession.getKieBase().getFactType( "chapter04.declaredTypes", "SpecialOrder"); Object instance = type.newInstance(); type.set(instance, "relevance", 2L); Object attr = type.get(instance, "relevance");
Property-reactive beans
lock-on-activate和no-loop只能针对object这个级别的变化,而propertyReactive属性可以去监视object的一个field值的变化。
declare PropertyReactiveOrder
@propertyReactive
discount: Discount
totalItems: Integer
total: Double
end
这个注解也可加到java类中,加在类级别上(不是field级别)。配合@Watch来使用
- @Watch(discount, total): This only monitors changes in the discount and total attributes
- @Watch(!discount): This means that we should disregard changes to the discount attribute of the bean
_注:_Property reactive类型的bean发生变化后,只能通过modify关键字来通知规则引擎,而不能用update,这也是update和modify的一个区别,所以建议使用modify。
Special Drools operations
Advanced conditional elements
之前的例子我们利用boolean操作符显式的去匹配一些条件,现在我们尝试一些更复杂的boolean操作。比如检查working memory中不同的对象是否匹配一些组合的条件。
NOT keyword
rule "warn about empty working memory"
when
not(Order() or Item())
then
System.out.println("we don't have elements");
end
以上的规则使用not,我们会确保在规则触发的那一刻,我们至少有一个Order或Item对象,否则将打印一个告警。 NOT与OR结合使用,它等价于下面的写法
rule "warn about empty working memory"
when
not(Order())
not(Item())
then
System.out.println("we don't have elements");
end
EXISTS and FORALL keywords
EXISTS用来检测working memory中是否存在对象
rule "with exists"
when
exists(Order())
then
System.out.prinltn("We have orders");
end
rule "without exists"
when
$o: Order()
then
System.out.println($o);
end
主要的不同就是,上面的规则只执行一次,而下面的规则working memory中有多少个Order对象,规则就匹配几次
forall中定义的条件都匹配forall()才是true
rule "make sure all orders have at least one line"
when
exists(Order())
forall(
Order()
Order(orderLines.size() > 0)
)
then
System.out.println("all orders have lines");
end
上面的例子中,exists检测working memory 中是否存在任何Order对象(只触发一次规则,不管Order对象有几个);forall中有两个条件,必须同时满足,forall才是true,forall结构检测内部的两个条件指定的Order集合是否完全相同。换句话说,working memory中存在的所有Order对象必须都满足orderLines.size()>0。
同时使用exists和forall的原因:如果不适用exists,而且working memory中不存在任何Order对象时,forall指定的两个条件是满足的,这样同样会触发规则。为了避免这种情况,使用exists约束后,就会排除了Order不存在这种情形。
Drools syntactic sugar
Nested accessors
OrderLine( item.cost < 30.0, item.salePrice < 25.0 )
比如上面的规则可以用下面的规则代替:
OrderLine( item.( cost < 30.0,salePrice < 25.0) )
Inline casts
Order(customer#SpecialCustomer.specialDiscount > 0.0)
Order(customer instanceof SpecialCustomer)
Order(customer.specialDiscount > 0.0)
Null-safe operators
Order(customer!.category != Category.NA)
上面的规则语句类似与下面的语句:
Order(customer != null,customer.category != Category.NA)
Decorating our objects in memory
修改working memory中的对象可以有:
修改,增加,删除对象,还可以用声明类型的对象,我们是否用声明类型的对象,主要有两种情况:
- 添加一个对象,代表域模型的引用
- 修改一个对象的属性,给这个属性添加推断值。
特别的第三种方式:使用trait,给已存在的对象添加一个天然的属性。
具体做法是:
定义一个trait,如
declare trait KidFriendly kidsAppeal: String end
给被添加属性的对象加注解@Traitable
Adding traits with the don keyword
rule "toy items are kid friendly"
no-loop
when $i: TraitableItem(name contains "toy")
then
KidFriendly kf = don($i, KidFriendly.class);
kf.setKidAppeal("can play with it");
end
TraitableItem必须用@Traitable来注解。
Removing traits with the shed keyword
如果我们想删除trait,我们可以使用shed关键字。
Object o = shed( $traitedObject, KidFriendly.class)
Logical insertion of elements
rule "determine large orders"
when $o: Order(total > 150)
then insert(new IsLargeOrder($o));
end
rule "determine large orders"
when $o: Order(total > 150)
then insertLogical(new IsLargeOrder($o));
end
上述例子中第二种属于逻辑插入,当条件变化了,比如total<150,这时,IsLargeOrder对象自动从working memory中消失。
Handling deviations of our rules
rule "large orders exception"
when $o: Order(total > 150, totalItems < 5)
then insertLogical(new IsLargeOrder($o), "neg");
end
上述例子表明,符合“total > 150, totalItems < 5”时,逻辑插入IsLargeOrder的反面,换句话说,当符合条件时,我们认为不是一个大订单(large order)。