1.什么是LHS?
左手边(LHS)是规则的条件部分的通用名称。它由零个或多个条件元素组成。如果LHS为空,则它将被视为始终为true的条件元素,并且在创建新的WorkingMemory会话时将激活一次。
rule "no CEs"
when
// empty
then
... // actions (executed once)
end
// 上面的rule可以用下面的写法代替:
rule "eval(true)"
when
eval( true )
then
... // actions (executed once)
end
1.隐含的and
rule "2 unconnected patterns"
when
Pattern1()
Pattern2()
then
... // actions
end
// The above rule is internally rewritten as:
rule "2 and connected patterns"
when
Pattern1()
and Pattern2()
then
... // actions
end
“and”不能具有前导声明绑定(与or不同
)。这是显而易见的,因为一个声明一次只能引用一个fact,当“and”满足时它匹配两个fact - 那么声明绑定到哪个fact?
// 编译错误
$person : (Person( name == "Romeo" ) and Person( name == "Juliet"))
2.匹配fact Pattern (conditional element)
可以将Pattern看作是一个class,用来匹配insert入working memory中的所有fact,(conditional element)中的conditional是在匹配到的fact中进行条件筛选,例如Person( age == 100 ),匹配的是working memory中的所有age==100的Person。
匹配到fact以后可以通过$ :来定义一个执行fact的私有变量。通过该变量对fact进行操作。因此,在规则匹配到执行完成,都不应对fact进行copy新建对象,而是操作原有的fact。
rule ...
when
$p : Person()
then
System.out.println( "Person " + $p );
end
3.约束Constraint (part of a pattern)
同Pattern,我们可以在圆括号里对fact进行约束。约束条件最终必须返回ture/false。在编写约束条件时,有几点需要注意。
3.1 尽量使用class私有变量,不要使用setter和getter方法。
Person( age == 50 )
// this is the same as:
Person( getAge() == 50 )
这两个匹配看以来是一致的,当时getAge()的底层实现我们是看不到的。如果在其内部对age进行了一系列修改可能会导致我们的误判。例如:
public int getAge() {
age++; // Do NOT do this
return age;
}
public int getAge() {
Date now = DateUtil.now(); // Do NOT do this
return DateUtil.differenceInYears(now, birthday);
}
3.2 支持嵌套的表达式
Person( address.houseNumber == 50 )
// this is the same as:
Person( getAddress().getHouseNumber() == 50 )
3.3 支持java表达式
您可以使用任何Java表达式boolean在模式的括号内返回约束作为约束。Java表达式可以与其他表达式增强功能混合使用,例如属性访问:
Person( age == 50 )
可以使用括号更改评估优先级,如在任何逻辑或数学表达式中一样:
Person( age > 100 && ( age % 10 == 0 ) )
可以重用Java方法:
Person( Math.round( weight / ( height * height ) ) < 25.0 )
3.4 对于属性访问器,方法不得以可能影响规则的方式更改对象的状态。对LHS中的事实执行的任何方法都应该是只读方法。
Person( incrementAndGetAge() == 10 ) // Do NOT do this
3.5 事实的状态不应在规则调用之间发生变化(除非这些事实在每次更改时都标记为更新到工作内存):
Person( System.currentTimeMillis() % 1000 == 0 ) // Do NOT do this
3.6 当约束中的值属性不匹配时将会抛出异常。
//throw "10" would coerce to a numeric 10.
Person( age == "10" )
3.7 ","分割代替and
// Person is at least 50 and weighs at least 80 kg
Person( age > 50, weight > 80 )
// Person is at least 50, weighs at least 80 kg and is taller than 2 meter.
Person( age > 50, weight > 80, height > 2 )
3.8 运算符“&&”,“ ||”,“ ,”优先级
尽管&&和,运算符具有相同的语义,但它们的解析具有不同的优先级:&&运算符优先于||运算符。&&和||运算符都在运算符,之前。请参阅下面的运算符优先级列表。
逗号运算符应该是顶级约束的首选,因为它使约束更容易阅读,并且Drools引擎通常能够更好地优化它们。逗号运算符不能嵌入到复合约束表达式中
Person( ( age > 50, weight > 80 ) || height > 2 ) // Do NOT do this: compile error
// Use this instead
Person( ( age > 50 && weight > 80 ) || height > 2 )
3.9 绑定变量
可以将属性定义给一个变量
// 2 persons of the same age
Person( $firstAge : age ) // binding
Person( age == $firstAge ) // constraint expression
前缀美元符号($)只是一个约定; 它可以在复杂的规则中使用,它有助于轻松区分变量和字段。
出于向后兼容性原因,允许(但不建议)混合约束绑定和约束表达式,如下所示:
// Not recommended
Person( $age : age * 2 < 100 )
// Recommended (separates bindings and constraint expressions)
Person( age * 2 < 100, $age : age )
3.10 内联#(验证完再补充)
3.11 日期文字支持
默认情况下的日期格式为dd-MMM-yyyy,可以通过修改系统属性来定制自己需要的格式,例如:drools.dateformat="dd-MMM-yyyy HH:mm"。也可以通过修改drools.defaultlanguage和drools.defaultcountry来制定语言区域,例如,泰国,drools.defaultlanguage=th
和drools.defaultcountry=TH。
Cheese( bestBefore < "27-Oct-2009" )
3.12 list和map访问
// Same as childList(0).getAge() == 18
Person( childList[0].age == 18 )
// Same as credentialMap.get("jsmith").isValid()
Person( credentialMap["jsmith"].valid )
3.13 Abbreviated combined relation condition 缩写组合关系条件
// Simple abbreviated combined relation condition using a single &&
Person( age > 30 && < 40 )
// Complex abbreviated combined relation using groupings
Person( age ( (> 30 && < 40) ||
(> 20 && < 25) ) )
// Mixing abbreviated combined relation with constraint connectives
Person( age > 30 && < 40 || location == "london" )
3.14 特殊的DRL操作符
操作符 < <= > >=
这些运算符可用于具有自然排序的属性。例如,对于日期字段,<表示之前,对于String字段,它表示按字母顺序较低
Person( firstName < $otherFirstName )
Person( birthDate < $otherBirthDate )
仅用于比较属性
3.15 空指针安全操作 "!."
这个运算发可以在属性不为空的情况新进行引用。例如:
Person( $streetName : address!.street )
//将被编译为
Person( address != null, $streetName : address.street )
3.16 运算符 matches
匹配任何有效Java正则表达式的字段。通常,regexp是字符串文字,但也允许解析为有效正则表达式的变量。与Java一样,写为字符串文字的正则表达式需要转义'\\'。仅适用于String
属性。matches
对null
值使用始终计算为false。
Cheese( type matches "(Buffalo)?\\S*Mozzarella" )
3.17 not matches 与matches相反
3.18 运算符 contains
运算符contains
用于检查作为Collection的字段或元素是否包含指定的值。仅适用于Collection
属性。
CheeseCounter( cheeses contains "stilton" ) // contains with a String literal
CheeseCounter( cheeses contains $var ) // contains with a variable
3.19 not contains 与contains相反
3.20 运算符 memberof
运算符memberOf
用于检查字段是否是集合或元素的成员; 该集合必须是一个变量。
CheeseCounter( cheese memberOf $matureCheeses )
3.21 not memeberof 与memberof相反
3.22 soundslike
类似于matches,相当于sql语句中的like,用来匹配是否具有相同字符。
// match cheese "fubar" or "foobar"
Cheese( name soundslike 'foobar' )
3.23 运算符str
此运算符str
用于检查String
以某个值开头或以某个值结尾的字段。它还可以用于检查String的长度。
Message( routingValue str[startsWith] "R1" )
Message( routingValue str[endsWith] "R2" )
Message( routingValue str[length] 17 )
3.24 in 和 not in
在有多个可能值匹配的情况下使用复合值限制。目前只有in
和not in
支持这一点。此运算符的第二个操作数必须是逗号分隔的值列表,括在括号中。值可以作为变量,文字,返回值或限定标识符给出。两个评估者实际上都是语法糖,内部重写为使用运算符!=
和多个限制的列表==
。
Person( $cheese : favouriteCheese )
Cheese( type in ( "stilton", "cheddar", $cheese ) )
3.25 内联eval运算符(已弃用)
内联eval约束可以使用任何有效的方言表达式,只要它产生一个原始布尔值。表达式必须随时间不变。可以使用来自当前或先前模式的任何先前绑定的变量; autovivification还用于自动创建字段绑定变量。当找到不是当前变量的标识符时,构建器将查看标识符是否是当前对象类型上的字段,如果是,则将字段绑定自动创建为同名变量。这称为内联eval内的字段变量的自动生成。内联eval实际上已经过时,因为它们的内部语法现在可以直接支持。建议不要使用它们。只需编写表达式而不包围eval()。
Person( girlAge : age, sex = "F" )
Person( eval( age == girlAge + 2 ), sex = 'M' )
// eval() is actually obsolete in this example
3.26 运算符的优先级:
Operator type | Operators | Notes |
---|---|---|
(nested / null safe) property access |
| 不是普通的Java语义 |
List/Map access |
| 不是普通的Java语义 |
constraint binding |
| 不是普通的Java语义 |
multiplicative |
| |
additive |
| |
shift |
| |
relational |
| |
equality |
| 注意不是java中的==/ !=,而是equal /not equal |
non-short circuiting AND |
| |
non-short circuiting exclusive OR |
| |
non-short circuiting inclusive OR |
| |
logical AND |
| |
logical OR |
| |
ternary |
| |
Comma separated AND |
| 不是普通的Java语义 |
4.位置参数
4.1 在约束属性时,可根据属性在fact中的位置来取消属性名称。
注意“;”很重要,如果约束完以后不加“;”,drools会把它当成一个布尔表达式。例如:
declare Cheese
name : String
shop : String
price : int
end
Person(name ==“mark”)可以写成 Person(“mark”;)
示例模式,具有两个约束和绑定。记住分号';' 用于区分位置部分和命名参数部分。位置参数支持仅使用文字的变量和文字以及表达式,但不支持变量。始终使用统一解决位置参数。
Cheese( "stilton", "Cheese Shop", p; )
Cheese( "stilton", "Cheese Shop"; p : price )
Cheese( "stilton"; shop == "Cheese Shop", p : price )
Cheese( name == "stilton"; shop == "Cheese Shop", p : price )
给定先前声明的绑定的位置参数将使用统一约束; 这些被称为输入参数。如果绑定尚不存在,它将创建将其绑定到position参数表示的字段的声明; 这些被称为输出参数。
5 细粒度的属性改变了Listeners
当您对给定对象调用modify()(请参阅modify语句部分)时,它将触发对KIE库中匹配对象类型的所有模式的重新评估。这可能导致不必要的和无用的评估,并且在最坏的情况下会导致无限递归。避免它的唯一解决方法是将对象拆分为与原始对象具有1对1关系的较小对象。
这已被引入以提供更容易和更一致的方式来克服该问题。实际上,它允许模式匹配仅对实际约束或绑定在给定模式内的属性的修改做出反应。这将有助于性能和递归,并避免人为的对象分裂。
默认情况下启用此功能,但是如果您需要或想要在特定bean上对其进行禁用,则可以使用@classReactive对其进行注释。
此注释适用于DRL类型声明:
declare Person
@classReactive
firstName : String
lastName : String
end
以及Java类:
@ClassReactive
public static class Person {
private String firstName;
private String lastName;
}
例如,通过使用此功能,如果您有如下规则:
rule "Every person named Mario is a male"
when
$person : Person( firstName == "Mario" )
then
modify ( $person ) { setMale( true ) }
end
你不必为它添加no-loop属性以避免无限递归,因为Drools引擎识别出模式匹配是在'firstName'属性上完成的,而规则的RHS修改了'male'属性。请注意,此功能不适用于update(),这是我们推荐modify()的原因之一,因为它封装了语句中的字段更改。此外,在Java类上,您还可以注释任何方法,以表明其调用实际上修改了其他属性。例如,在Person类中,您可以使用以下方法
@Modifies( { "firstName", "lastName" } )
public void setName(String name) {
String[] names = name.split("\\s");
this.firstName = names[0];
this.lastName = names[1];
}
这意味着如果规则具有如下的RHS:
modify($person) { setName("Mario Fusco") }
它将正确识别两个属性'firstName'和'lastName'的值可能已经被修改并相应地采取行动,而不是缺少重新评估约束它们的模式。目前,@Modifies不允许使用在字段上,而只允许使用在方法上。这与最常见的情况一致,其中@Modifies将用于与类字段无关的方法,如前一示例中的Person.setName()。另请注意,@Modifies不具有传递性,这意味着如果另一个方法在内部调用Person.setName(),则使用@Modifies({“name”})对其进行注释是不够的,但是必须使用@Modifies({“firstName”,“lastName”})。很可能@Modifies传递性将在下一版本中实现。
对于嵌套访问器的问题,Drools引擎仅会通知顶级字段。换句话说,模式匹配如下:
Person ( address.city.name == "London )
将仅为修改Person对象的'address'属性而重新评估。以同样的方式,约束分析目前严格限于模式内部的内容。另一个例子可以帮助澄清这一点。LHS如下:
$p : Person( )
Car( owner = $p.name )
这样不会监听对Person name的修改,但下面这样会监听:
Person( $name : name )
Car( owner = $name )
要解决这个问题,可以使用@watch注释模式,如下所示:
$p : Person( ) @watch ( name )
Car( owner = $p.name )
@watch 允许通过约束条件来控制监听哪些字段。例如:
// listens for changes on both firstName (inferred) and lastName
Person( firstName == $expectedFirstName ) @watch( lastName )
// listens for all the properties of the Person bean
Person( firstName == $expectedFirstName ) @watch( * )
// listens for changes on lastName and explicitly exclude firstName
Person( firstName == $expectedFirstName ) @watch( lastName, !firstName )
// listens for changes on all the properties except the age one
Person( firstName == $expectedFirstName ) @watch( *, !age )
由于在Pattern上使用@ClassReactive没有意义,因此如果您尝试这样做,规则编译器将引发编译错误。另外,@ watch中相同属性的重复使用(例如:@ watch(firstName,!firstName))将最终出现编译错误。在下一个版本中,我们将通过在模式之外进行分析来自动检测要更智能地监听的属性。
也可以仅在特定类型的模型上启用此功能,或者通过使用KnowledgeBuilderConfiguration的on选项完全禁用此功能。特别是这个新的PropertySpecificOption可以具有以下3个值之一:
- DISABLED => 此功能已关闭,所有其他相关注释都将被忽略
- ALLOWED =>类型不是属性反应型,除非它们没有用@PropertyReactive(@ClassReactive的对偶)注释
types are not property reactive unless they are not annotated with @PropertyReactive (which is the dual of @ClassReactive)
- ALWAYS => 所有类型都是属性相关的. 这是默认的。
因此,例如,要拥有一个默认禁用属性反应性的KnowledgeBuilder:
KnowledgeBuilderConfiguration config = KnowledgeBuilderFactory.newKnowledgeBuilderConfiguration();
config.setOption(PropertySpecificOption.ALLOWED);
KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder(config);
在最后一种情况下,可以通过使用@PropertyReactive注释特定类型的属性反应性功能来重新启用它。
有一点很重要:要注意属性反应性仅对在规则结果内执行的修改自动可用。相反,程序更新不知道已更改的对象属性,因此无法使用此功能。要解决此限制,可以在update语句中指定已修改对象中已更改的属性的名称,如以下示例所示
Person me = new Person("me", 40);
FactHandle meHandle = ksession.insert( me );
me.setAge(41);
me.setAddress("California Avenue");
ksession.update( meHandle, me, "age", "address" );
6 基本条件元素
6.1 条件元素 and
条件元素"and"
用于将其他条件元素分组为逻辑连接。Drools支持前缀and
和中缀and
6.1.1 and
支持传统中缀:
//infixAnd
Cheese( cheeseType : type ) and Person( favouriteCheese == cheeseType )
还支持使用括号进行显式分组:
//infixAnd with grouping
( Cheese( cheeseType : type ) and
( Person( favouriteCheese == cheeseType ) or
Person( favouriteCheese == cheeseType ) )
不推荐使用该符号&&
(作为替代and
)。但是在向后兼容性的语法中仍然支持它。
6.1.2 and
还支持前缀:
(and Cheese( cheeseType : type )
Person( favouriteCheese == cheeseType ) )
LHS的根元素是隐式前缀and
,不需要指定:
when
Cheese( cheeseType : type )
Person( favouriteCheese == cheeseType )
then
...
6.2 条件元素 or
条件元素or
用于将其他条件元素分组为逻辑分离。Drools支持前缀or
和中缀or
。
6.2.1 or
支持传统中缀:
//infixOr
Cheese( cheeseType : type ) or Person( favouriteCheese == cheeseType )
还支持使用括号进行显式分组:
//infixOr with grouping
( Cheese( cheeseType : type ) or
( Person( favouriteCheese == cheeseType ) and
Person( favouriteCheese == cheeseType ) )
不推荐使用该符号||
(作为替代or
)。但是在向后兼容性的语法中仍然支持它。
6.2.2 or
还支持前缀:
(or Person( sex == "f", age > 60 )
Person( sex == "m", age > 65 )
)
注意:条件元素的行为or
不同于||
字段约束中的约束和限制的连接。Drools引擎实际上并不了解条件元素or
。用来代替的是,通过许多不同的逻辑转换,规则or
被重写为多个子规则。此过程最终会产生一个规则,该规则具有单个or
作为根节点和每个条件元素的一个子规则。每个子规则都可以像任何正常规则一样激活和触发; 这些子规则之间没有特殊的行为或相互作用。 - 这对新规则作者来说可能是最令人困惑的。
6.2.3 条件元素or
还允许可选的模式绑定。这意味着每个生成的子规则将其模式绑定到模式绑定。每个模式必须使用同名变量单独绑定:
pensioner : ( Person( sex == "f", age > 60 ) or Person( sex == "m", age > 65 ) )
(or pensioner : Person( sex == "f", age > 60 )
pensioner : Person( sex == "m", age > 65 ) )
由于条件元素or
导致多个子规则生成,每个可能的逻辑结果一个,上面的示例将导致内部生成两个规则。这两个规则在工作记忆中独立工作,这意味着两者都可以match, activate and fire - 没有捷径。
考虑条件元素的最佳方法or
是生成两个或多个类似规则的快捷方式。当你这样想的时候,很明显,对于单一规则,如果两个或多个分离项是真的,那么可能会有多次激活
6.3 条件元素 not
not
是一阶逻辑的非存在量词,并检查Working Memory中某些事物的不存在。认为“不”意思是“一定不能......”。
关键字not
后面可以跟适用于它的条件元素的括号。在单个模式的最简单情况下(如下所示),您可以选择省略括号。
not Bus()
// Brackets are optional:
not Bus(color == "red")
// Brackets are optional:
not ( Bus(color == "red", number == 42) )
// "not" with nested infix and - two patterns,
// brackets are requires:
not ( Bus(color == "red") and
Bus(color == "blue") )
6.4 条件元素 exists
exists
是一阶逻辑的存在量词,并检查Working Memory中存在的东西。将“存在”想象为“至少有一个......”。它不仅仅是拥有自己的模式,更像是“为每一个......”。如果使用exists
模式,则规则最多只激活一次,无论工作内存中有多少数据匹配exists
模式内部的条件。由于只有存在才重要,因此不会建立任何约束。
关键字not
后面可以跟适用于它的条件元素的括号。在单个模式的最简单情况下(如下所示),您可以选择省略括号。
exists Bus()
exists Bus(color == "red")
// brackets are optional:
exists ( Bus(color == "red", number == 42) )
// "exists" with nested infix and,
// brackets are required:
exists ( Bus(color == "red") and
Bus(color == "blue") )
7 高级条件元素
7.1 条件元素forall
条件元素forall
完成了Drools中的First Order Logic支持。forall
当与第一个约束匹配的所有事实与所有剩余模式匹配时,条件元素的计算结果为true。例:
rule "All English buses are red"
when
forall( $bus : Bus( type == 'english')
Bus( this == $bus, color = 'red' ) )
then
// all English buses are red
end
在上面的规则中,我们“选择”所有类型为“english”的Bus对象。然后,对于匹配此模式的每个事实,我们评估以下模式,如果它们匹配,则forall将评估为true。
为了说明工作存储器中给定类型的所有事实必须与一组约束匹配,forall
为简单起见,可以用单个模式写入。例:
rule "All Buses are Red"
when
forall( Bus( color == 'red' ) )
then
// all Bus facts are red
end
另一个例子显示了内部的多个模式forall
:
rule "all employees have health and dental care programs"
when
forall( $emp : Employee()
HealthCare( employee == $emp )
DentalCare( employee == $emp )
)
then
// all employees have health and dental care
end
Forall可以嵌套在其他条件元素中。例如,forall
可以在not
内部使用。请注意,只有单个模式具有可选括号,因此forall
必须使用嵌套括号:
rule "not all employees have health and dental care"
when
not ( forall( $emp : Employee()
HealthCare( employee == $emp )
DentalCare( employee == $emp ) )
)
then
// not all employees have health and dental care
end
作为旁注,forall( p1 p2 p3…)
相当于写作:
not(p1 and not(and p2 p3...))
此外,重要的是要注意这forall
是一个范围分隔符。因此,它可以使用任何先前绑定的变量,但其中的变量绑定不可用于其外部。
7.2 条件元素 from
条件元素from
使用户能够为LHS模式匹配的数据指定任意源。这允许Drools引擎对不在Working Memory.中的数据进行推理。数据源可以是绑定变量上的子字段或方法调用的结果。它是一种强大的结构,允许与其他应用程序组件和框架进行开箱即用的集成。一个常见的例子是使用hibernate命名查询与数据库按需检索的数据集成。
用于定义对象源的表达式是遵循常规MVEL语法的任何表达式。因此,它允许您轻松使用对象属性导航,执行方法调用以及访问地图和集合元素。
这是一个推理和绑定另一个模式子字段的简单示例:
rule "validate zipcode"
when
Person( $personAddress : address )
Address( zipcode == "23920W") from $personAddress
then
// zip code is ok
end
凭借Drools引擎中新表现力的所有灵活性,您可以通过多种方式切割和切割此问题。这是相同的,但显示了如何使用带有'from'的图表符号:
rule "validate zipcode"
when
$p : Person( )
$a : Address( zipcode == "23920W") from $p.address
then
// zip code is ok
end
以前的例子是使用单一模式的评估。from
还支持返回对象源的对象集合。在这种情况下,from
将迭代集合中的所有对象并尝试单独匹配它们中的每一个。例如,如果我们想要一个规则对订单中的每个item应用10%的折扣,我们可以这样做:
rule "apply 10% discount to all items over US$ 100,00 in an order"
when
$order : Order()
$item : OrderItem( value > 100 ) from $order.items
then
// apply discount to $item
end
上面的示例将导致规则为每个给定订单的值大于100的项目触发一次。
但是,在使用时必须小心,from
特别是与lock-on-active
rule属性一起使用时,因为它可能会产生意外结果。考虑前面提供的示例,但现在稍微修改如下:
rule "Assign people in North Carolina (NC) to sales region 1"
ruleflow-group "test"
lock-on-active true
when
$p : Person( )
$a : Address( state == "NC") from $p.address
then
modify ($p) {} // Assign person to sales region 1 in a modify block
end
rule "Apply a discount to people in the city of Raleigh"
ruleflow-group "test"
lock-on-active true
when
$p : Person( )
$a : Address( city == "Raleigh") from $p.address
then
modify ($p) {} // Apply discount to person in a modify block
end
在上面的例子中,北卡罗来纳州罗利市的人员应被分配到销售区域1并获得折扣; 也就是说,你会期望两个规则都被激活和激活。相反,你会发现只有第二条规则才会触发。
如果要打开审核日志,您还会看到当第二个规则触发时,它会停用第一个规则。由于规则属性lock-on-active
阻止规则在一组事实发生更改时创建新的激活,因此第一个规则无法重新激活。虽然这组事实没有改变,但from
每次评估时,使用都会为所有意图和目的返回一个新事实。
首先,重要的是要回顾为什么要使用上述模式。您可能在不同的 rule-flow组中有许多rule。当规则修改working memory和RuleFlow下的其他规则(在不同的rule-flow组中)需要重新评估时,使用modify
是至关重要的。但是,您不希望同一rule-flow组中的其他规则以递归方式将激活放在另一个规则流上。在这种情况下,该no-loop
属性是无效的,因为它只会阻止规则以递归方式激活自身。因此,你求助于lock-on-active
。
有几种方法可以解决此问题:
- 当你可以将所有事实断言到working memory或在约束表达式中使用嵌套对象引用时,避免使用
from
(如下所示) - 将在修改块中使用的已分配变量作为条件(LHS)中的最后一个句子。
- 当您可以明确管理同一rule-flow组中的规则如何相互激活时,请避免使用
lock-on-active
(如下所述)
首选解决方案是当可以将所有事实直接置于工作内存中时,尽可能减少使用from
。在上面的示例中,Person和Address实例都可以声明为工作内存。在这种情况下,由于图表非常简单,更简单的解决方案是修改规则,如下所示:
rule "Assign people in North Carolina (NC) to sales region 1"
ruleflow-group "test"
lock-on-active true
when
$p : Person(address.state == "NC" )
then
modify ($p) {} // Assign person to sales region 1 in a modify block
end
rule "Apply a discount to people in the city of Raleigh"
ruleflow-group "test"
lock-on-active true
when
$p : Person(address.city == "Raleigh" )
then
modify ($p) {} //Apply discount to person in a modify block
end
现在,您将发现两个规则都按预期启动。但是,并不总是可以访问上面嵌套的事实。考虑一个人拥有一个或多个地址的示例,并且您希望使用exists 来匹配具有至少一个满足特定条件的地址的人。在这种情况下,您将不得不求助于对from
集合进行推理。
有几种方法可以from
用来实现这一目标,并非所有方法都表现出使用的问题lock-on-active
。例如,以下使用from
会导致两个规则按预期触发:
rule "Assign people in North Carolina (NC) to sales region 1"
ruleflow-group "test"
lock-on-active true
when
$p : Person($addresses : addresses)
exists (Address(state == "NC") from $addresses)
then
modify ($p) {} // Assign person to sales region 1 in a modify block
end
rule "Apply a discount to people in the city of Raleigh"
ruleflow-group "test"
lock-on-active true
when
$p : Person($addresses : addresses)
exists (Address(city == "Raleigh") from $addresses)
then
modify ($p) {} // Apply discount to person in a modify block
end
但是,以下略有不同的方法确实表现出问题:
rule "Assign people in North Carolina (NC) to sales region 1"
ruleflow-group "test"
lock-on-active true
when
$assessment : Assessment()
$p : Person()
$addresses : List() from $p.addresses
exists (Address( state == "NC") from $addresses)
then
modify ($assessment) {} // Modify assessment in a modify block
end
rule "Apply a discount to people in the city of Raleigh"
ruleflow-group "test"
lock-on-active true
when
$assessment : Assessment()
$p : Person()
$addresses : List() from $p.addresses
exists (Address( city == "Raleigh") from $addresses)
then
modify ($assessment) {} // Modify assessment in a modify block
end
在上面的例子中,$ addresses变量是从使用中返回的from
。该示例还引入了一个新对象,即评估,以突出显示这种情况下的一种可能的解决方案。如果条件(LHS)中分配的$ assessment变量被移动到每个规则中的最后一个条件,则两个规则都会按预期触发。
虽然上述实施例说明了如何使用结合from
与lock-on-active
其中没有规则的激活丢失时,它们携带放置依赖于LHS条件的顺序上的缺点。此外,在跟踪哪些条件可能产生问题方面,解决方案为规则作者提供了更大的复杂性。
更好的选择是断言更多事实放入working memory。在这种情况下,person’s addresses可以被放入working memory,并且不需要使用from
。
但是,有些情况下,将所有数据声明为工作内存是不切实际的,我们需要找到其他解决方案。另一种选择是重新评估是否需要lock-on-active
。另一种方法lock-on-active
是直接管理同一规则流组中的规则如何通过在每个规则中包含条件来激活彼此激活,这些条件阻止规则在修改工作内存时递归地相互激活。例如,在上面对Raleigh公民应用折扣的情况下,可以在检查是否已经应用折扣的规则中添加条件。如果是,则规则不会激活。
包含from子句的模式不能跟随以括号开头的另一个模式,如下例所示
rule R when
$l : List( )
String() from $l
(String() or Number())
then end
这是因为在这种情况下,DRL解析器将from表达式从“$ l(String()或Number())”中读取,并且不可能从函数调用中消除该表达式的歧义。对此的直接解决方法是在括号中包含from子句,如下所示:
rule R when
$l : List( )
(String() from $l)
(String() or Number())
then end
7.3 条件元素collect
条件元素collect
允许规则对从给定源或working memory获得的对象集合进行推理。在First Oder Logic术语中,这是基数量词。一个简单的例子:
import java.util.ArrayList
rule "Raise priority if system has more than 3 pending alarms"
when
$system : System()
$alarms : ArrayList( size >= 3 )
from collect( Alarm( system == $system, status == 'pending' ) )
then
// Raise priority, because system $system has
// 3 or more alarms pending. The pending alarms
// are $alarms.
end
在上面的示例中,规则将在每个给定 system的working memory中查找所有pending的alarms,并将它们分组到ArrayLists中。如果找到给定system的3个或更多alarms,则该规则将触发。
结果模式collect
可以是实现java.util.Collection
接口的任何具体类,并提供默认的无参数公共构造函数。这意味着您可以使用Java集合,如ArrayList,LinkedList,HashSet等,或者您自己的类,只要它实现java.util.Collection
接口并提供默认的无参数公共构造函数即可。
源和结果模式都可以被约束为任何其他模式。
在collect
之前绑定的变量属于源模式和结果模式的范围,因此您可以使用它们来约束源模式和结果模式。但请注意,collect
是绑定的范围分隔符,因此在其中进行的任何绑定都不可用于collect之外。
Collect接受嵌套的from
。以下示例是“collect”的有效用法:
import java.util.LinkedList;
rule "Send a message to all mothers"
when
$town : Town( name == 'Paris' )
$mothers : LinkedList()
from collect( Person( gender == 'F', children > 0 )
from $town.getPeople()
)
then
// send a message to all mothers
end
7.4条件元素 accumulate
Accumulate 是一种更灵活和更强大的collect,在某种意义上他提供了不仅可以实现collect的功能还提供了额外的功能。Accumulate允许规则迭代对象集合,为每个元素执行自定义操作,最后返回结果对象。
Accumulate支持使用预定义的累积函数或使用内联自定义代码。但是应该避免内联自定义代码,因为规则作者更难维护,并且经常导致代码重复。Accumulate functions 更易于测试和重用。
Accumulate还支持多种不同的语法。首选语法是顶级accumulate,如下所述,但支持所有其他语法以实现向后兼容。
Accumulate(首选语法)
顶级Accumulate语法是最紧凑和灵活的语法。简化的语法如下:
accumulate( <source pattern>; <functions> [;<constraints>] )
例如,计算给定传感器的最小,最大和平均温度读数的规则如果最低温度低于20摄氏度且平均值超过70摄氏度则会发出警报,可以使用累积:
rule "Raise alarm"
when
$s : Sensor()
accumulate( Reading( sensor == $s, $temp : temperature );
$min : min( $temp ),
$max : max( $temp ),
$avg : average( $temp );
$min < 20, $avg > 70 )
then
// raise the alarm
end
在上面的示例中,min,max和average是累计函数,并将计算每个传感器的所有读数的最小,最大和平均温度值。
Drools配有多种内置累积功能,包括:
-
average
-
min
-
max
-
count
-
sum
-
variance
-
standardDeviation
-
collectList
-
collectSet
这些常用函数接受任何表达式作为输入。例如,如果有人想要计算订单所有item的平均利润,则可以使用平均函数编写规则:
rule "Average profit"
when
$order : Order()
accumulate( OrderItem( order == $order, $cost : cost, $price : price );
$avgProfit : average( 1 - $cost / $price ) )
then
// average profit for $order is $avgProfit
end
累积功能都是可插拔的。这意味着,如果需要,可以轻松地将自定义的特定于域的功能添加到Drools引擎中,并且规则可以开始使用它们而没有任何限制。要实现一个新的Accumulate Function,所有人需要做的就是创建一个实现该org.kie.api.runtime.rule.AccumulateFunction
接口的Java类。作为Accumulate Function实现的示例,以下是该average
函数的实现:
/**
* An implementation of an accumulator capable of calculating average values
*/
public class AverageAccumulateFunction implements org.kie.api.runtime.rule.AccumulateFunction<AverageAccumulateFunction.AverageData> {
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
}
public void writeExternal(ObjectOutput out) throws IOException {
}
public static class AverageData implements Externalizable {
public int count = 0;
public double total = 0;
public AverageData() {}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
count = in.readInt();
total = in.readDouble();
}
public void writeExternal(ObjectOutput out) throws IOException {
out.writeInt(count);
out.writeDouble(total);
}
}
/* (non-Javadoc)
* @see org.kie.api.runtime.rule.AccumulateFunction#createContext()
*/
public AverageData createContext() {
return new AverageData();
}
/* (non-Javadoc)
* @see org.kie.api.runtime.rule.AccumulateFunction#init(java.io.Serializable)
*/
public void init(AverageData context) {
context.count = 0;
context.total = 0;
}
/* (non-Javadoc)
* @see org.kie.api.runtime.rule.AccumulateFunction#accumulate(java.io.Serializable, java.lang.Object)
*/
public void accumulate(AverageData context,
Object value) {
context.count++;
context.total += ((Number) value).doubleValue();
}
/* (non-Javadoc)
* @see org.kie.api.runtime.rule.AccumulateFunction#reverse(java.io.Serializable, java.lang.Object)
*/
public void reverse(AverageData context, Object value) {
context.count--;
context.total -= ((Number) value).doubleValue();
}
/* (non-Javadoc)
* @see org.kie.api.runtime.rule.AccumulateFunction#getResult(java.io.Serializable)
*/
public Object getResult(AverageData context) {
return new Double( context.count == 0 ? 0 : context.total / context.count );
}
/* (non-Javadoc)
* @see org.kie.api.runtime.rule.AccumulateFunction#supportsReverse()
*/
public boolean supportsReverse() {
return true;
}
/* (non-Javadoc)
* @see org.kie.api.runtime.rule.AccumulateFunction#getResultType()
*/
public Class< ? > getResultType() {
return Number.class;
}
}
正如我们所预料的那样,函数的代码非常简单,因为所有“脏”集成工作都是由Drools引擎完成的。最后,要使用规则中的函数,作者可以使用“import accumulate”语句导入它:
import accumulate <class_name> <function_name>
例如,如果实现了some.package.VarianceFunction
实现该variance
函数的类函数并希望在规则中使用它,他将执行以下操作:
import accumulate some.package.VarianceFunction variance
rule "Calculate Variance"
when
accumulate( Test( $s : score ), $v : variance( $s ) )
then
// the variance of the test scores is $v
end
内置函数(总和,平均值等)由Drools引擎自动导入。只需显式导入用户定义的自定义累加函数。
为了向后兼容,Drools仍然支持通过配置文件和系统属性配置累积功能,但这是一种不推荐使用的方法。为了使用配置文件或系统属性配置上一个示例中的方差函数,用户可以设置如下属性:
drools.accumulate.function.variance = some.package.VarianceFunction
请注意,“ drools.accumulate.function.
”是必须始终使用的前缀,“ variance
”是如何在drl文件中使用该函数,“ some.package.VarianceFunction
”是实现函数行为的类的完全限定名称。
accumulate
的语法随着时间的推移而演变,目标是变得更加紧凑和富有表现力。尽管如此,Drools仍支持以前的语法以实现向后兼容。
如果规则在给定的累积上使用单个累加函数,则作者可以为结果对象添加模式,并使用“from”关键字将其链接到累积结果。示例:对超过100美元的订单应用10%折扣的规则可以通过以下方式编写:
rule "Apply 10% discount to orders over US$ 100,00"
when
$order : Order()
$total : Number( doubleValue > 100 )
from accumulate( OrderItem( order == $order, $value : value ),
sum( $value ) )
then
// apply discount to $order
end
在上面的示例中,accumulate元素仅使用一个函数(sum),因此,规则作者选择显式地为accumulate函数(Number)的结果类型编写模式,并在其中写入约束。使用这种语法比以前提出的紧凑语法没有问题,除了有点冗长。另请注意,不允许在同一个accumulate语句中同时使用返回类型和函数绑定。
执行编译时检查以确保与“ from
”关键字一起使用的模式可从所使用的累积函数的结果中分配。
使用此语法,“ from
”绑定到accumulate函数返回的单个结果,并且不会迭代。
在上面的例子中,“ $total
”绑定到accumulate sum()函数返回的结果。
但是,作为另一个例子,如果accumulate函数的结果是一个集合,“ from
”仍然绑定到单个结果,它不会迭代:
rule "Person names"
when
$x : Object() from accumulate(MyPerson( $val : name );
collectList( $val ) )
then
// $x is a List
end
绑定的“ $x : Object()
”是List本身,由使用的collectList累积函数返回。
这是要强调的重要区别,因为“ from
”关键字也可以单独使用accumulate来迭代集合的元素:
rule "Iterate the numbers"
when
$xs : List()
$x : Integer() from $xs
then
// $x matches and binds to each Integer in the collection
end
虽然出于向后兼容的目的仍支持此语法,但出于此原因和其他原因,我们鼓励规则作者使用而不是使用Accumulate首选语法(在前一章中描述),以避免任何潜在的陷阱,如这些示例所述。
7.5 Accumulate with inline custom code
由于多种原因使用累积内联自定义代码并不是一种好的做法,包括维护和测试使用它们的规则的困难,以及无法重用该代码。实现自己的累积函数非常简单直接,易于单元测试和使用。这种形式的累积仅支持向后兼容。
累积的另一种可能的语法是定义内联自定义代码,而不是使用累积函数。如前所述,虽然出于上述原因,但不鼓励这样做。
具有内联自定义代码的accumulate
的一般语法是:
<result pattern> from accumulate( <source pattern>,
init( <init code> ),
action( <action code> ),
reverse( <reverse code> ),
result( <result expression> ) )
每个元素的含义如下:
-
<source pattern>:源模式是Drools引擎将尝试与每个源对象匹配的常规模式。
-
<init code>:这是所选方言中的代码语义块,在迭代源对象之前,每个元组将执行一次。
-
<action code>:这是将为每个源对象执行的所选方言中的语义代码块。
-
<reverse code>:这是所选方言中的可选语义代码块,如果存在,则将对不再与源模式匹配的每个源对象执行。此代码块的目标是撤消在<action code>块中执行的任何计算,以便Drools引擎可以在修改或删除源对象时进行递减计算,从而极大地提高了这些操作的性能。
-
<result expression>:这是在所有方言中迭代所有源对象后执行的语义表达式。
-
<result pattern>:这是Drools引擎尝试与<result expression>返回的对象匹配的常规模式。如果匹配,则
accumulate
条件元素的计算结果为true,Drools引擎继续评估规则中的下一个条件元素。如果不匹配,则accumulate
评估为false,并且Drools引擎停止评估该规则的条件元素。
如果我们看一个例子,它会更容易理解:
rule "Apply 10% discount to orders over US$ 100,00"
when
$order : Order()
$total : Number( doubleValue > 100 )
from accumulate( OrderItem( order == $order, $value : value ),
init( double total = 0; ),
action( total += $value; ),
reverse( total -= $value; ),
result( total ) )
then
// apply discount to $order
end
在上面的示例中,Drools引擎对于工作内存中的每个Order
,将执行init代码,将total变量初始化为零。然后它将迭代该订单的所有对象OrderItem
,为每个对象执行操作(在该示例中,它将所有项的值汇总到总变量中)。迭代所有OrderItem
对象后,它将返回与结果表达式相对应的值(在上例中,变量的值total
)。最后,Drools引擎将尝试将结果与Number
模式匹配,如果double值大于100,则规则将触发。
该示例使用Java作为语义方言,因此请注意,在init,action和reverse代码块中必须使用分号作为语句分隔符。结果是表达式,因此,它不承认';'。如果用户使用任何其他方言,他必须遵守该方言的特定语法。
如前所述,<reverse code>是可选的,但强烈建议用户编写它以便从更新和删除的改进性能中受益。
该accumulate
可以被用来执行对源对象的任何性位。以下示例实例化并填充自定义对象:
rule "Accumulate using custom objects"
when
$person : Person( $likes : likes )
$cheesery : Cheesery( totalAmount > 100 )
from accumulate( $cheese : Cheese( type == $likes ),
init( Cheesery cheesery = new Cheesery(); ),
action( cheesery.addCheese( $cheese ); ),
reverse( cheesery.removeCheese( $cheese ); ),
result( cheesery ) );
then
// do something
end
8 条件元素eval
条件元素eval
本质上是一个全能型,它允许执行任何语义代码(返回一个原始布尔值)。此代码可以引用规则的LHS中绑定的变量,以及规则包中的函数。过度使用eval会降低规则的声明性,并可能导致性能不佳的引擎。虽然eval
可以在模式中的任何位置使用,但最佳做法是将其添加为规则的LHS中的最后一个条件元素。
Evals无法编入索引,因此不如Field Constraints高效。但是,这使得它们非常适合在函数返回随时间变化的值时使用,这在Field Constraints中是不允许的。
对于熟悉Drools 2.x血统的人来说,旧的Drools参数和条件标记等同于将变量绑定到适当的类型,然后在eval节点中使用它。
p1 : Parameter()
p2 : Parameter()
eval( p1.getList().containsKey( p2.getItem() ) )
p1 : Parameter()
p2 : Parameter()
// call function isValid in the LHS
eval( isValid( p1, p2 ) )