议程通过rete算法实现。它维护了多组规则的执行,并规划这些规则的执行顺序。
当操作处于RuleRuntime阶段时,若规则完全匹配则有资格被执行,单一的规则执行后可能导致多个规则有资格被执行。当符合规则条件时,会将该规则放入议程。议程通过冲突解决策略来控制这些规则的执行顺序。
发动机循环经过两个阶段:
1. 规则运行阶段。大多数的工作在这里进行,包括Consequence (RHS本身)和java main方法。一旦Consequence或是java main 调用了fireAllRules()
,引擎将会切换到议程评估阶段
2. 议程评估阶段。该阶段试图选择一个规则去执行。如果没有合适的规则,则该阶段结束,否则将会执行一个合适的规则,并进入到规则运行阶段。
下图为两个阶段的执行过程:
重复该过程直到议程清楚,在这种情况下,控制返回到调用应用程序。当正在执行规则运行时操作时,不会触发任何规则。
到目前为止,数据和匹配过程一直很简单。为了混合起来,将探索一个新的例子来处理日期期间的现金流量计算。将在关键阶段说明性地显示发动机的状态,以帮助更好地了解发动机罩下的实际情况。将使用三个类,如下所示。这将有助于我们增加对模式匹配的理解并进一步加入。然后我们将使用它来说明执行控制的不同技术。
public class CashFlow {
private Date date;
private double amount;
private int type;
long accountNo;
// getter and setter methods here
}
public class Account {
private long accountNo;
private double balance;
// getter and setter methods here
}
public AccountPeriod {
private Date start;
private Date end;
// getter and setter methods here
}
到目前为止,您已经知道如何创建KieBase以及如何实例化填充KieSession,为了更清晰地表示,我们将数据以表的形式展示。下表显示为此插入了一个Account实例。同时还插入了该账户两个季度的贷方与借方现金流记录。
两个规则可以被用来定义该季度贷方与借方并更新账户余额。下面这两条规则约束了规定时间段内某一个账户的现金流。注意”&&”使用了简短的语法来避免的变量的重复命名。
rule "increase balance for credits"
when
ap : AccountPeriod()
acc : Account( $accountNo : accountNo )
CashFlow( type == CREDIT,
accountNo == $accountNo,
date >= ap.start && <= ap.end,
$amount : amount )
then
acc.balance += $amount;
end
rule "decrease balance for debits"
when
ap : AccountPeriod()
acc : Account( $accountNo : accountNo )
CashFlow( type == DEBIT,
accountNo == $accountNo,
date >= ap.start && <= ap.end,
$amount : amount )
then
acc.balance -= $amount;
end
之前,我们展示了规则如何等同于SQL,以此帮助有SQL基础的人理解规则。这两个规则可以被表示为两个视图,每个视图配一个触发器,如下所示:
select * from Account acc,
Cashflow cf,
AccountPeriod ap
where acc.accountNo == cf.accountNo and
cf.type == CREDIT and
cf.date >= ap.start and
cf.date <= ap.end
trigger : acc.balance += cf.amount
select * from Account acc,
Cashflow cf,
AccountPeriod ap
where acc.accountNo == cf.accountNo and
cf.type == DEBIT and
cf.date >= ap.start and
cf.date <= ap.end
trigger : acc.balance -= cf.amount
如果AccountPeriod被设置为第一个季度,那么规则”increase balance for credits”将会影响两条数据,规则“”decrease balance for debits”将会影响一条数据。
上图两个现金流表中展示的数据是与规则所匹配的,数据在插入阶段是匹配的。这些规则不会立即执行,只有在fireAllRules()
被调用后。同时,规则与它匹配的数据被放在议程中,作为一个规则实例。议程中存放多条规则实例,在调用fireAllRules()
后,才会执行他们的结果。规则实例在议程中被称为一个冲突集,他们的执行由冲突解决策略决定。注意现在执行的顺序被认为是随机的。
当上面所有的规则被执行后,账户上的余额变成了-25。
如果AccountPeriod
变成第二季度,我们只有一条匹配数据和一个规则实例在议程中。
规则执行后余额变为25。
如果你不想规则的执行顺序是随机的,该怎么办?当有多条规则实例在议程中时,他们有可能存在冲突,会有一个冲突解决策略被用来决定执行顺序。Drools的策略非常简单,它会在规则中指定一个优先级。每个规则都有一个默认的优先级0,这个值越高优先级也就越高。
为了说明优先级我们添加一个规则来打印账户余额,我们希望这条规则在所有规则之后执行。我们对它赋予一个负值以保证他在所有默认值为0的规则后面。
rule "Print balance for AccountPeriod"
salience -50
when
ap : AccountPeriod()
acc : Account()
then
System.out.println( acc.accountNo + " : " + acc.balance );
end
下图描述在议程中规则的执行顺序。前三条借款与贷款规则为随机执行,打印余额则是在最后被执行。
议程组允许你将规则以组的形式存放,并将这些组放到堆栈上。堆栈具有push\pop操作。通过调用setFocus()
将组放到栈上:
ksession.getAgenda().getAgendaGroup( "Group A" ).setFocus();
议程总是在栈的顶部开始执行。当一个议程组中的所有规则都被执行后,这个议程组会从栈顶移除然后执行下一组。
rule "increase balance for credits"
agenda-group "calculation"
when
ap : AccountPeriod()
acc : Account( $accountNo : accountNo )
CashFlow( type == CREDIT,
accountNo == $accountNo,
date >= ap.start && <= ap.end,
$amount : amount )
then
acc.balance += $amount;
end
rule "Print balance for AccountPeriod"
agenda-group "report"
when
ap : AccountPeriod()
acc : Account()
then
System.out.println( acc.accountNo + " : " + acc.balance );
end
根据栈“先进后出”的特性,先设置”report”,再设置”calculation”,以保证”calculation”被第一个执行。
Agenda agenda = ksession.getAgenda();
agenda.getAgendaGroup( "report" ).setFocus();
agenda.getAgendaGroup( "calculation" ).setFocus();
ksession.fireAllRules();