Drools规则引擎技术指南
上QQ阅读APP看书,第一时间看更新

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所示。

044-1

图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所示。证明比较符是可以直接进行常量比较操作的。

045-1

图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所示。

046-1

图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所示。

047-1

图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方法。

048-1

图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所示。

049-1

图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所示。

050-1

图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所示。

051-1

图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所示。

053-1

图3-14 matches匹配操作结果

简单来说就是模糊查询语法不能是“*.三”,否则会抛出语法错误。该规则用来查找所有Person对象的name属性是不是以“张”字开头,若条件成立,则输出正确结果。使用$符号也是成立的,$符号是指“.”后一位,而“*”则可以多位。但如果写成“**”就会抛出语法错误。$符号只会匹配一次,虽然$$符号不会报错,但规则不成立。例如,将“person.setName("张小三");”分别测试规则为“张.$$”“张.**”两种,如图3-15所示。

053-2

图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所示。

055-1

图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所示。

056-1

图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所示。

057-1

图3-18 str测试结果