3.5 约束连接
匹配模式中可以有多种约束符的连接,常用的有“&&”(and)、“||”(or)和“,”(and)。这3个连接符号如果没有用括号来显示定义的优先级,那么“&&”优先级大于“||”优先级。从表面上看“,”与“&&”具有相同的含义,但是在Drools 6.4版本中,“,”与“&&”和“||”不能混合使用,即在有“&&”或“||”出现的LHS部分,是不可以有“,”连接符出现的,反之亦然。可能是Drools规则引擎的研发人员觉得这样做并不好,所以在Drools 7.10版本进行测试后竟然可以同时存在了。
Drools提供了12种类型比较操作符,如果进行常量比较,必须通过函数或引用比较对象属性进行比较,不能单独使用,其中>或<,>=或<=,==或!= 语法与Java是一样的。
本节的核心内容是Drools自带的约束条件,共有6种比较操作符(共三组),每一组操作相同、功能相反,其内容为:
contains| not contains memberOf | not memberOf matches | not matches
1. contains比较操作符
contains是用来检查一个Fact对象的某个属性值是否包含一个指定的对象值。其语法格式为:
Object( field[Collection/Array] contains|not contains value)
创建School.java类,放在pojo包下,其代码为:
package com.pojo; public class School { private String className; private String classCount; public String getClassName() { return className; } public void setClassName(String className) { this.className = className; } public String getClassCount() { return classCount; } public void setClassCount(String classCount) { this.classCount = classCount; } }
修改Person.java类,添加属性className,其代码为:
package com.pojo;
public class Person {
private String name;//姓名
private int age;//年龄
private String className;//所在班级
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
}
创建规则文件contains.drl的第一种写法,目录为rules/constraint/isContains,并添加以下代码:
package rules.constraint.isContains;
import com.pojo.Person;
import com.pojo.School;
rule containsTest
when
$s:School();
$p:Person(className contains $s.className);
then
System.out.println("恭喜你,成功地使用了 contains");
end
修改kmodule.xml配置文件,添加如下配置:
<kbase name="contains" packages="rules.constraint.isContains"> <ksession name="contains"/> </kbase>
创建执行调用规则代码RulesConstraint类,目录为com/rulesConstraint,并添加以下代码:
package com.rulesConstraint; import com.pojo.Person; import com.pojo.School; import org.junit.Test; import org.kie.api.KieServices; import org.kie.api.runtime.KieContainer; import org.kie.api.runtime.KieSession; public class RulesConstraint{ @Test public void contains() { KieServices kss = KieServices.Factory.get(); KieContainer kc = kss.getKieClasspathContainer(); KieSession ks = kc.newKieSession("contains"); Person person = new Person(); person.setName("张三"); person.setAge(30); person.setClassName("一班"); School school = new School(); school.setClassName("一班"); ks.insert(person); ks.insert(school); int count = ks.fireAllRules(); System.out.println("总执行了" + count + "条规则"); ks.dispose(); } }
执行contains()方法查看输出结果,如图3-6所示。
图3-6 contains第一种写法测试结果
第二种写法与第一种相似,只不过使用的Fact对象是get方法,其内容为:
package rules.constraint.isContains;
import com.pojo.Person;
import com.pojo.School;
rule containsTest
when
$s:School();
$p:Person(className contains $s.getClassName());
then
System.out.println("恭喜你,成功地使用了 contains");
end
结果与图3-6所示是一样的,使用第二种写法时不能忽略get,只写className()是错误的。
添加rule containsTest002,将contains的第二个参数变成一个常量,其内容为:
rule containsTest002 when $s:School(); $p:Person(className contains "一班"); then System.out.println("规则 containsTest002 恭喜你,成功地使用了 contains"); end
执行调用规则代码,结果如图3-7所示。证明比较符是可以直接进行常量比较操作的。
图3-7 contains常量比较
2. not contains比较运算符
not contains的作用与contains相反,它是用来判断一个Fact对象的某个字段不包含一个指定的对象。
修改规则文件,其内容为:
rule containsTest003 when $s:School(); $p:Person(className contains $s.className); then $s.setClassName("二班"); update($s) end rule containsTest004 when $s:School(); $p:Person(className not contains $s.className); then System.out.println("规则 containsTest004 恭喜你,成功地使用了 not contains"); end
在containsTest003规则中修改School Fact对象的值,并在rule containsTest004中进行比较。执行调用规则代码结果如图3-8所示。
图3-8 not contains测试结果
若删除规则体containsTest003,则规则体containsTest004不会执行。
3. memberOf比较运算符
memberOf用来判断某个Fact对象的某个字段是否在一个或多个集合中。
memberOf的语法为:
Object(fieldName memberOf|not memberOf value[Collection/Array])
修改School.java文件,添加String[] classNameArray属性并实现get set方法。
创建规则文件memberOf.dr[3]第一种写法,目录为rules/constraint/isMemberOf,并添加以下代码:
package rules.constraint.isMemberOf;
import com.pojo.Person;
import com.pojo.School;
rule memberOfTest001
when
$s:School();
$p:Person(className memberOf $s.getClassName());
then
System.out.println("恭喜你,成功地使用了 memberOf");
end
修改kmodule.xml配置文件,添加如下配置:
<kbase name="memberOf" packages="rules.constraint.isMemberOf"> <ksession name="memberOf"/> </kbase>
添加执行调用规则,其代码为:
@Test
public void memberOfArray() {
KieServices kss = KieServices.Factory.get();
KieContainer kc = kss.getKieClasspathContainer();
KieSession ks = kc.newKieSession("memberOf");
Person person = new Person();
person.setName("张三");
person.setAge(30);
person.setClassName("一班");
School school = new School();
school.setClassNameArray(new String[]{"一班","二班","三班"});
ks.insert(person);
ks.insert(school);
int count = ks.fireAllRules();
System.out.println("总执行了" + count + "条规则");
ks.dispose();
}
执行memberOf Array()方法查看输出结果,如图3-9所示。
图3-9 memberOf测试结果
经过上述测试结果,添加两个规则,如memberOfTest002、memberOfTest003,其内容为:
rule memberOfTest002 when $s:School(); $p:Person(className memberOf "一班"); then System.out.println("恭喜你 memberOfTest002,成功地使用了 memberOf"); end rule memberOfTest003 when $s:School(); $p:Person(className memberOf $s.className); then System.out.println("恭喜你 memberOfTest003,成功地使用了 memberOf"); end
修改执行调用规则代码并运行,其代码为:
@Test
public void memberOfArray() {
KieServices kss = KieServices.Factory.get();
KieContainer kc = kss.getKieClasspathContainer();
KieSession ks = kc.newKieSession("memberOf");
Person person = new Person();
person.setName("张三");
person.setAge(30);
person.setClassName("一班");
School school = new School();
school.setClassName("一班");
school.setClassNameArray(new String[]{"一班","二班","三班"});
ks.insert(person);
ks.insert(school);
int count = ks.fireAllRules();
System.out.println("总执行了" + count + "条规则");
ks.dispose();
}
执行结果如图3-10所示,常量匹配是没有问题的,但使用属性对象值则不行。如果猜测正确,在使用常量情况下,memberOf第二个参数会转换为数组。
测试完数组后,下面分别对List、Set、Map进行测试。
① 测试List,修改School.java文件,添加“private List classNameList;”成员变量并实现get set方法。
图3-10 memberOf扩展测试
添加rule memberOfTest004,其内容为:
rule memberOfTest004
when
$s:School();
$p:Person(className memberOf $s.classNameList);
then
System.out.println("恭喜你 memberOfTest004,成功地使用了 memberOf");
end
添加测试方法,其代码为:
@Test
public void memberOfList() {
KieServices kss = KieServices.Factory.get();
KieContainer kc = kss.getKieClasspathContainer();
KieSession ks = kc.newKieSession("memberOf");
Person person = new Person();
person.setName("张三");
person.setAge(30);
person.setClassName("一班");
School school = new School();
List classNameList = new ArrayList();
classNameList.add("一班");
classNameList.add("二班");
classNameList.add("三班");
school.setClassNameList(classNameList);
ks.insert(person);
ks.insert(school);
int count = ks.fireAllRules();
System.out.println("总执行了" + count + "条规则");
ks.dispose();
}
执行Java方法memberOfList(),输出结果如图3-11所示。
图3-11 memberOfList测试结果
② 测试Set,修改School.java文件,添加“private List classNameSet;”成员变量并实现get set方法。
添加规则memberOfTest005,其内容为:
rule memberOfTest005
when
$s:School();
$p:Person(className memberOf $s.classNameSet);
then
System.out.println("恭喜你 memberOfTest005,成功地使用了 memberOf");
end
添加测试方法,其内容为:
@Test
public void memberOfSet() {
KieServices kss = KieServices.Factory.get();
KieContainer kc = kss.getKieClasspathContainer();
KieSession ks = kc.newKieSession("memberOf");
Person person = new Person();
person.setName("张三");
person.setAge(30);
person.setClassName("一班");
School school = new School();
Set classNameSet = new HashSet();
classNameSet.add("一班");
classNameSet.add("二班");
classNameSet.add("三班");
school.setClassNameSet(classNameSet);
ks.insert(person);
ks.insert(school);
int count = ks.fireAllRules();
System.out.println("总执行了" + count + "条规则");
ks.dispose();
}
执行Java方法memberOfSet(),输出结果如图3-12所示。
图3-12 memberOf Set测试结果
③ 测试Map,修改School.java文件,添加“private List classNameMap;”成员变量并实现get set方法。
添加规则memberOfTest006,其内容为:
rule memberOfTest006
when
$s:School();
$p:Person(className memberOf $s.classNameMap);
then
System.out.println("恭喜你 memberOfTest006,成功地使用了 memberOf");
end
添加测试方法,其代码为:
@Test
public void memberOfMap() {
KieServices kss = KieServices.Factory.get();
KieContainer kc = kss.getKieClasspathContainer();
KieSession ks = kc.newKieSession("memberOf");
Person person = new Person();
person.setName("张三");
person.setAge(30);
person.setClassName("一班");
School school = new School();
Map classNameMap = new HashMap();
classNameMap.put("一班","1");
classNameMap.put("二班","2");
classNameMap.put("三班","3");
school.setClassNameMap(classNameMap);
ks.insert(person);
ks.insert(school);
int count = ks.fireAllRules();
System.out.println("总执行了" + count + "条规则");
ks.dispose();
}
执行Java方法memberOfMap(),输出结果如图3-13所示。
图3-13 memberOf Map测试结果
使用Map是一个有争议的测试,本例就结果而言是没有问题的,但是一旦将Map赋值的代码变更,如将Map中的Key设置为班级编号,value设置为班级名称,那么结果memberOfTest006将不会输出。即使用memerOf比较符操作Map集合时,比较是否存在Key。如果一个Map的Key都不存在,就不用提Value了。
4. not memberOf比较运算符
not memberOf与memberOf的作用恰恰相反,是用来判断Fact对象中某个字段值不在某个集合中。
添加规则memberOfTest007,并执行memberOfMap测试方法,测试结果不再阐述。
rule memberOfTest007
when
$s:School();
$p:Person(className not memberOf $s.classNameMap);
then
System.out.println("恭喜你 memberOfTest007,成功地使用了 memberOf");
end
5. matches比较运算符
matches用来对某个Fact对象的字段与标准的Java正则表达式进行相似匹配,被比较的字符串可以是一个标准的Java正则表达式。但需要读者注意的是,正则表达式字符串中不用考虑“\”的转义问题,其语法为:
Object(fieldName matches | not matches "正则表达式")
创建规则文件matches.drl,并添加如下代码:
package rules.constraint.isMatches;
import com.pojo.Person;
rule matchesTest001
when
$p:Person(name matches "张.*");
then
System.out.println("恭喜你,成功地使用了 matches");
end
修改kmodule.xml配置文件,并添加如下配置:
<kbase name="memberOf" packages="rules.constraint.isMemberOf"> <ksession name="memberOf"/> </kbase>
添加测试方法,其代码为:
@Test public void matches() { KieServices kss = KieServices.Factory.get(); KieContainer kc = kss.getKieClasspathContainer(); KieSession ks = kc.newKieSession("matches"); Person person = new Person(); person.setName("张三"); person.setAge(30); person.setClassName("一班"); ks.insert(person); int count = ks.fireAllRules(); System.out.println("总执行了" + count + "条规则"); ks.dispose(); }
执行结果如图3-14所示。
图3-14 matches匹配操作结果
简单来说就是模糊查询语法不能是“*.三”,否则会抛出语法错误。该规则用来查找所有Person对象的name属性是不是以“张”字开头,若条件成立,则输出正确结果。使用$符号也是成立的,$符号是指“.”后一位,而“*”则可以多位。但如果写成“**”就会抛出语法错误。$符号只会匹配一次,虽然$$符号不会报错,但规则不成立。例如,将“person.setName("张小三");”分别测试规则为“张.$$”“张.**”两种,如图3-15所示。
图3-15 使用两个通配符的结果
通过上述测试得出一个结论:matches的第二个参数是字符串,并且可以进行中文匹配。以“.”作为分隔符,比较符第二个参数进行模糊匹配,即“*”部分,并且必须以“.”的方式隔开,英文字符“,”同理。以“.”隔开的前面部分也必须是insert参数属性中的一部分。
简单地说,就是根据insert参数属性内容进行以“.”为分隔符的正则匹配比较运算功能。在英文模式下使用$符号并不可行。
图3-15所示的结果是通过测试得出的。官方解释说matches可以匹配任何有效Java正则表达式的字段。通常regexp是字符串文字,但也允许解析为有效正则表达式的变量。下面通过测试进行总结。
Cheese( type matches "(Buffalo)?\\S*Mozzarella" ) 第一次测试: 修改person.setName()为person.setName("zs"), 修改规则$p:Person(name matches "(zs)"); 执行matches()方法,输出结果与图3-14一样 第二次测试: 修改person.setName()为person.setName("zs"), 修改规则$p:Person(name matches "(z)"); 执行matches()方法,输出结果是规则没有成立 第三次测试: 修改person.setName()为person.setName("zs"), 修改规则$p:Person(name matches "(z)[a-z]"); 执行matches()方法,输出结果与图3-14一样 第四次测试: 修改person.setName()为person.setName("zs"), 修改规则$p:Person(name matches "(zs)[a-z]"); 执行matches()方法,输出结果是规则没有成立 第五次测试: 修改person.setName()为person.setName("zs"), 修改规则$p:Person(name matches "(z|s)[a-z]"); 执行matches()方法,输出结果与图3-14一样 第六次测试: 修改person.setName()为person.setName("zs"), 修改规则$p:Person(name matches "(z|s|l)[a-z]");与 修改规则$p:Person(name matches "(z|s|)[a-z]"); 执行matches()方法,输出结果与图3-14一样 第七次测试: 修改person.setName()为person.setName("zsx"), 修改规则$p:Person(name matches "(z|s|x)[a-z]"); 执行matches()方法,输出结果是规则没有成立 第八次测试: 修改person.setName()为person.setName("zsx"), 修改规则$p:Person(name matches "(z|*)[a-z]"); 执行matches()方法,输出结果是规则语法错误 第九次测试: 修改person.setName()为person.setName("zsx"), 修改规则$p:Person(name matches "(z.*)[a-z]"); 执行matches()方法,输出结果与图3-14一样
上述测试结果中与第一个例子明显不同,即参数加了“()”。“()”的作用类似于增加了匹配优先级,先进行正则匹配,如果正则配置成功,就会进行上述说明的insert参数属性内容的匹配。至于“|”符号,在第七次测试及后来的测试中,通过执行结果发现“|”与“||”和“&&”功能相似,都是进行逻辑判断的。
6. not matches比较运算符
not matches的作用与matches相反,是用来将某个Fact对象的字段与一个Java标准正则表达式进行匹配,若与正则表达式不匹配,则规则成立。
添加规则matchesTest002,其内容为:
rule matchesTest002 when $p:Person(name not matches "(zs && s.*)[a-z]"); then System.out.println("恭喜你,成功地使用了 not matches"); end
运行matches()方法,结果如图3-16所示。
图3-16 not matches测试结果
7. soundslike比较运算符
soundslike用来检查单词是否具有与给定值几乎相同的声音(使用英语发音)。基于Soundex算法的语法为:
Object(fieldName soundslike 'value')
创建规则文件soundslike.drl,目录为rules/constraint/isSoundslike,并添加如下代码:
package rules.constraint.isSoundslike;
import com.pojo.Person;
rule isSoundslikeTest001
when
$p:Person(name soundslike "foobar");
then
System.out.println("恭喜你,成功地使用了 isSoundslike");
end
修改kmodule.xml配置文件,并添加如下配置:
<kbase name="soundslike" packages="rules.constraint.isSoundslike"> <ksession name="soundslike"/> </kbase>
修改RulesConstraint.java文件,并添加测试方法,其代码为:
@Test public void soundslike() { KieServices kss = KieServices.Factory.get(); KieContainer kc = kss.getKieClasspathContainer(); KieSession ks = kc.newKieSession("soundslike"); Person person = new Person(); person.setName("fubar"); person.setAge(30); person.setClassName("一班"); ks.insert(person); int count = ks.fireAllRules(); System.out.println("总执行了" + count + "条规则"); ks.dispose(); }
执行soundslike()方法,结果如图3-17所示。
图3-17 soundslike测试结果
8. str比较运算符
str不仅检查String字段是否以某一值开头/结尾,还可以判断字符串长度,其语法为:
Object(fieldName str[startsWith|endsWith|length] "String"|1)
创建规则文件str.drl,目录为rules/constraint/isStr,并添加如下代码:
package rules.constraint.isStr;
import com.pojo.Person;
rule strTest001
when
$p:Person(name str[startsWith] "张");
then
System.out.println("恭喜你,成功地使用了 str startsWith");
end
rule strTest002
when
$p:Person(name str[endsWith] "三");
then
System.out.println("恭喜你,成功地使用了 str endsWith");
end
rule strTest003
when
$p:Person(name str[length] 3);
then
System.out.println("恭喜你,成功地使用了 isSoundslike");
end
修改kmodule.xml配置文件,并添加如下配置:
<kbase name="isStr" packages="rules.constraint.isStr"> <ksession name="isStr"/> </kbase>
修改RulesConstraint.java文件,并添加测试方法,其代码为:
@Test public void str() { KieServices kss = KieServices.Factory.get(); KieContainer kc = kss.getKieClasspathContainer(); KieSession ks = kc.newKieSession("isStr"); Person person = new Person(); person.setName("张小三"); person.setAge(30); person.setClassName("一班"); ks.insert(person); int count = ks.fireAllRules(); System.out.println("总执行了" + count + "条规则"); ks.dispose(); }
执行str()方法,结果如图3-18所示。
图3-18 str测试结果