Chapter 4

主要讨论的问题

  • 规则文件读取和修改数据的几种方式
  • 用属性,global变量以及一些其他特性来配置规则
  • 讨论drools规则的执行怎样被其他其他机制来控制

用global变量添加外部交互

chapter-04-kjar 中ruleAttributes/classify-item-rules.drl

修改working memory中的数据

  1. 拆更小的规则
  2. 我们不能在一个规则中调用另一个规则,所以只能修改或添加数据来使其他规则重新计算它们,看是否可以触发条件。

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

这种解决办法有两个问题:

  1. 对象始终需要维护一个和本身内容无关的变量flag,唯一的用途和规则的执行有关。
  2. 规则如果很多,那么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对象,否则将打印一个告警。 NOTOR结合使用,它等价于下面的写法

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中的对象可以有:
修改,增加,删除对象,还可以用声明类型的对象,我们是否用声明类型的对象,主要有两种情况:

  1. 添加一个对象,代表域模型的引用
  2. 修改一个对象的属性,给这个属性添加推断值。

特别的第三种方式:使用trait,给已存在的对象添加一个天然的属性。

具体做法是:

  1. 定义一个trait,如

     declare trait KidFriendly
       kidsAppeal: String
     end
    
  2. 给被添加属性的对象加注解@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)。

results matching ""

    No results matching ""