4 规则
4.5 LHS 语法
4.5.1 LHS 简介
在规则文件组成章节,我们已经了解了 LHS 的基本使用说明。LHS 是规则条件部分的 统称,由 0 个或多个条件元素组成。前面我们已经提到,如果没有条件元素那么默认就是 true。
没有条件元素,官方示例:
rule "no CEs"
when
// empty then
... // actions (executed once) end
// The above rule is internally rewritten as:
rule "eval(true)"
when
eval( true ) then
... // actions (executed once) end
如果有多条规则元素,默认它们之间是“和”的关系,也就是说必须同时满足所有的条件 元素才会触发规则。官方示例:
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
和“or”不一样,“and”不具有优先绑定的功能。因为生命一次只能绑定一个 FACT 对象,
而当使用 and 时就无法确定声明的变量绑定到哪个对象上了。以下代码会编译出错。
$person : (Person( name == "Romeo" ) and Person( name == "Juliet"))
4.5.2 Pattern (条件元素)
Pattern 元素是最重要的一个条件元素,它可以匹配到插入 working memory 中的每个 FACT 对象。一个 Pattern 包含 0 到多个约束条件,同时可以选择性的进行绑定。
通过上图可以明确的知道 Pattern 的使用方式,左边变量定义,然后用冒号分割。右边 pattern 对象的类型也就是 FACT 对象,后面可以在括号内添加多个约束条件。最简单的一 种形式就是,只有 FACT 对象,没有约束条件,这样一个 pattern 配到指定的 patternType 类 即可。
比如,下面的 pattern 定义表示匹配 Working Memory 中所有的 Person 对象。
Person()
pattemType 并不需要使用实际存在的 FACT 类,比如下面的定义表示匹配 Working Memory 中所有的对象。很明显,Object 是所有类的父类。
Object() // 匹配 working memory 中的所有对象
如下面的示例,括号内的表达式决定了当前条件是否会被匹配到,这也是实际应用中最 常见的使用方法。
Person( age == 100 )
Pattern 绑定:当匹配到对象时,可以将 FACT 对象绑定到指定的变量上。这里的用法类 似于 java 的变量定义。绑定之后,在后面就可以直接使用此变量。
rule ...
when
$p : Person() then
System.out.println( "Person " + $p );
end
其中前缀$只是一个约定标识,有助于在复杂的规则中轻松区分变量和字段,但并不强 制要求必须添加此前缀。
4.5.3 约束(Pattern 的一部分)
前面我们已经介绍了条件约束在 Pattern 中位置了,那么什么是条件约束呢?简单来说就 是一个返回 true 或者 false 的表达式,比如下面的 5 小于 6,就是一个约束条件。
Person( 5 < 6 )
从本质上来讲,它是 JAVA 表达式的一种增强版本(比如属性访问),同时它又有一些 小的区别,比如 equals 方法和==的语言区别。下面我们就深入了解一下。
访问 JavaBean 中的属性
任何一个 JavaBean 中的属性都可以访问,不过对应的属性要提供 getter 方法或 isProperty 方法。比如:
Person( age == 50 )
// 与上面拥有同样的效果 Person( getAge() == 50 )
Drools 使用 java 标准的类检查,因此遵循 java 标准即可。同时,嵌套属性也是支持的,
比如:
Person( address.houseNumber == 50 )
// 与上面写法相同
Person( getAddress().getHouseNumber() == 50 )
在使用有状态 session 的情况下使用嵌套属性需要注意属性的值可能被其他地方修改。
要么认为它们是不可变的,当任何一个父引用被插入到 working memory 中。或者,如果要 修改嵌套属性值,则应将所有外部 fact 标记更新。在上面的例子中,当 houseNumber 属性 值改变时,任何一个包含 Address 的 Person 需要被标记更新。
Java 表达式
在 pattern 的约束条件中,可以任何返回结果为布尔类型的 java 表达式。当然,java 表 达式也可以和增强的表达式进行结合使用,比如属性访问。可以通过使用括号来更改计算优 先级,如在任一逻辑或数学表达式中。
Person( age > 100 && ( age % 10 == 0 ) )
也可以直接使用 java 提供的工具方法来进行操作计算:
Person( Math.round( weight / ( height * height ) ) < 25.0 )
在使用的过程中需要注意,在 LHS 中执行的方法只能是只读的,不能在执行方法过程 中改变 FACT 对象的值,否则会影响规则的正确执行。
Person( incrementAndGetAge() == 10 ) //不要像这样在比较的过程中更新 Fact 对象 另外,FACT 对象的相关状态除了在 working memory 中可以进行更新操作,不应该在 每次调用时使状态发生变化。
Person( System.currentTimeMillis() % 1000 == 0 ) // 不要这样实现
标准 Java 运算符优先级也适用于此处,详情参考下面的运算符优先级列表。所有的操 作符都有标准的 Java 语义,除了==和!=。它们是 null 安全的,就相当于 java 中比较两个 字符串时把常量字符串放前面调用 equals 方法的效果一样。
约束条件的比较过程中是会进行强制类型转换的,比如在数据计算中传入字符串“10”,
则能成功转换成数字 10 进行计算。如果传入的值无法进行转换,比如传了“ten”,会抛出异 常。
逗号分隔符
逗号可以对约束条件进行分组,它的作用相当于“AND”。
// Person 的年龄要超过 50,并且重量 超过 80 kg Person( age > 50, weight > 80 )
虽然“&&”和“,”拥有相同的功能,但是它们有不同的优先级。“&&”优先于“||”,“&&”和“||”
又优先于“,”。建议优先使用“,”分隔符,因为它更利于阅读理解和引擎的操作优化。同时,逗 号分隔符不能和其他操作符混合使用,比如:
Person( ( age > 50, weight > 80 ) || height > 2 ) // 会编译错误
// 使用此种方法替代
Person( ( age > 50 && weight > 80 ) || height > 2 )
绑定变量
一个属性可以绑定到一个变量:
// 2 person 的 age 属性值相同 Person( $firstAge : age ) // 绑定
Person( age == $firstAge ) // 约束表达式
前缀$只是个通用惯例,在复杂规则中可以通过它来区分变量和属性。为了向后兼容,
允许(但不推荐)混合使用约束绑定和约束表达式。
// 不建议这样写
Person( $age : age * 2 < 100 )
// 推荐(分离绑定和约束表达式)
Person( age * 2 < 100, $age : age )
使用操作符“==”来绑定变量,Drools 会使用散列索引来提高执行性能。
内部类分组访问
通常情况,我们访问一个内部类的多个属性时会有如下的写法:
Person( name == "mark", address.city == "london", address.country == "uk" ) Drools 提供的分组访问可以更加方便进行使用:
Person( name == "mark", address.( city == "london", country == "uk") )
注意前缀'.'是用来区分嵌套对象约束和方法调用所必需的。
内部强制转换
在使用内部类的时候,往往需要将其转换为父类,在规则中可以通过“#”来进行强制转 换:
Person( name == "mark", address#LongAddress.country == "uk" )
上面的例子将 Address 强制转换为 LongAddress.,使得 getter 方法变得可用。如果无 法强制转换,表达式计算的结果为 false。强制转换也支持全路径的写法:
Person( name == "mark", address#org.domain.LongAddress.country == "uk" ) 多次内部转换语法:
Person( name == "mark", address#LongAddress.country#DetailedCountry.population >
10000000 )
也可以使用 instanceof 操作符进行判断,判断之后将进一步使用该属性进行比较。
Person( name == "mark", address instanceof LongAddress, address.country == "uk" )
日期字符
规则语法中除了支持 JAVA 标准字符,同时也支持日期字符。Drools 默认支持的日期格 式为“dd-mmm-yyyy”,可以通过设置系统变量“drools.dateformat”的值来改变默认的日期格 式。
Cheese( bestBefore < "27-Oct-2009" )
List 和 Map 的访问
访问 List:
// 和 childList(0).getAge() == 18 效果相同 Person( childList[0].age == 18 )
根据 key 访问 map:
// 和 credentialMap.get("jsmith").isValid()相同 Person( credentialMap["jsmith"].valid )
&&和||
约束表达式可以通过&&和||来进行判断比较,用法与标准 Java 相似,当组合使用时,
可通过括号来区分优先级。
Person( age > 30 && < 40 )
Person( age ( (> 30 && < 40) ||(> 20 && < 25) ) ) Person( age > 30 && < 40 || location == "london" )
DRL 特殊操作符
“< ⇐ > >=”操作符用于属性的比较时按照默认的排序,比如日期属性使用小于号比较,
将按照日期前后排序;当使用在 String 字符串的比较时,则按照字母顺序进行排序。此操作 符仅适用于可进行比较的属性值。
Person( firstName < $otherFirstName ) Person( birthDate < $otherBirthDate )
“!.”提供了一个默认空校验的操作。使用此操作符时,会先校验当前对象是否为 null,
如果不为 null 再调用其方法或属性进行判断。一旦当前操作对象为 null,则相当于判断结果 为 false。
Person( $streetName : address!.street )
// 上面的写法相当于
Person( address != null, $streetName : address.street )
matches 操作符可使用 Java 的正则表达式进行字符串的匹配,通常情况下使用正则表 达式字符串进行匹配,但也支持变量值为正确的表达式的方式。此操作符仅适用于字符串属 性。如果属性值为 null,匹配的结果始终为 false。
Cheese( type matches "(Buffalo)?\\S*Mozzarella" )
not matches 方法与 matches 相同,唯一不同的是返回的结果与之相反。
Cheese( type not matches "(Buffalo)?\\S*Mozzarella" )
contains 操作符判断一个集合属性或元素是否包含指定字符串或变量值。仅适用于集合 属性。也可以用于替代 String.contains()来检查约束条件。not contains 用法与之相同,结果 取反。
CheeseCounter( cheeses contains "stilton" ) // 包含字符串 CheeseCounter( cheeses contains $var ) // 包含变量
Cheese( name contains "tilto" ) Person( fullName contains "Jr" ) String( this contains "foo" )
memberOf 用来检查属性值是否为集合,此集合的表示必须为变量。not memberOf 使 用方法相同,结果取反。
CheeseCounter( cheese memberOf $matureCheeses )
soundslike 的效果与 matches 相似,但它用来检查一个字符串的发音是否与指定的字符 十分相似(使用英语发音)。
// 匹配 "fubar" 或 "foobar"
Cheese( name soundslike 'foobar' )
str 操作用来比较一个字符串是否以指定字符串开头或结尾,有可以用于比较字符串的 长度。
Message( routingValue str[startsWith] "R1" ) Message( routingValue str[endsWith] "R2" ) Message( routingValue str[length] 17 )
in 和 notin 用来匹配一组数据中是否含一个或多个匹配的字符串,使用的方法与数据库 中 in 的使用方法相似。待匹配的数据可以是字符串、变量。
Person( $cheese : favouriteCheese )
Cheese( type in ( "stilton", "cheddar", $cheese ) )
运算符优先级
操作类型 操作符 备注
(嵌套/空安全)属性访问 .!. 非标准 java 语义
List/Map 访问 [ ] 非标准 java 语义
约束绑定 : 非标准 java 语义
乘除 \*/%
加减 \+-
移位 <<>>>>>
关系 <>⇐>=instanceof
等 ==!= 未使用标准 java 语义,某些
语义相当于 equals。
非短路 AND &
非短路异或 ^
非短路包含 OR |
逻辑与 &&
逻辑或 ||
三元运算符 ? :
逗号分隔,相当于 and , 非标准 java 语义
4.5.4 其他语法
其他不常用语法读者可自行查阅官方文档使用,后续补充相关内容。