侧边栏壁纸
  • 累计撰写 106 篇文章
  • 累计创建 3 个标签
  • 累计收到 19 条评论
标签搜索

目 录CONTENT

文章目录

Drools入门之规则when

卑微幻想家
2022-07-04 / 0 评论 / 0 点赞 / 79 阅读 / 13,734 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2022-07-04,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

复合值限制in/not in

复合值限制是指超过一种匹配值的限制条件,如Sql语句中的in,语法格式与sql多匹配相似。以括号为第二参数,括号内比较值以逗号分隔,比较值可以是变量、文字、返回值或标识符等,其内部功能与“!=”“==”运算符的多限制列表类似。

public class RulesWhen {
    public static void main(String[] args) {
        KieServices kieService = KieServices.Factory.get();
        KieContainer kieContainer = kieService.getKieClasspathContainer();
        KieSession kieSession = kieContainer.newKieSession("testWhen");
        Person person1 = new Person();
        person1.setClassName("三班");
        kieSession.insert(person1);

        kieSession.fireAllRules();
        kieSession.dispose();
    }
}
rule "in rule1"
    when
        Person(className in ("一班","二班","三班"))
    then
        System.out.println("验证in的复合限制规则");
end

条件元素eval

条件元素eval是在最开始的测试用例中就使用过的。它可以是任何语义代码,并返回一个boolean类型,也可以绑定规则LHS中的变量、函数或直接写常量进行比较。但在实际编码过程中,要尽可能地少用eval作为比较符,因为会导致引擎的性能问题。读者可以参考书中经典Hello World章节中的用例,或者在funcation函数中进行测试。

条件元素not

条件元素not是判断在工作内存中是否还存在某个值,当not EC成立时就代表当前工作内存中不存在EC,可以看成“一定没有这个值”。

rule "not rule"
    when
        not School()
    then
        System.out.println("不存在School");
end

条件元素exists

条件元素exists的功能与not的功能是相反的,指在工作内存中是否存在某个东西,可以看作“至少有一个”。

rule "exists rule"
    when
        exists Person()
    then
        System.out.println("存在Person");
end

条件元素forall

条件元素forall的功能与eval()的功能是相似的,通过模式匹配对forall进行一个判断,当完全匹配时,forall为true。

rule "for all"
    when
        forall($p:Person(name=="张三") Person(this == $p,age == 30))
    then
        System.out.println("测试forall");
end

条件元素from

条件元素from是一个很有意思的约束,它可以让用户指定任意的资源,用于LHS部分的数据匹配,也可以用来对集合进行遍历,还可以用来对Java服务进行访问并就结果进行遍历。

新建一个Teacher类

package com.domain;

public class Teacher {
    private String teacherName;

    public Teacher(){}

    public Teacher(String teacherName) {
        this.teacherName = teacherName;
    }

    public String getTeacherName() {
        return teacherName;
    }

    public void setTeacherName(String teacherName) {
        this.teacherName = teacherName;
    }
}
public class RulesWhen {
    public static void main(String[] args) {
        KieServices kieService = KieServices.Factory.get();
        KieContainer kieContainer = kieService.getKieClasspathContainer();
        KieSession kieSession = kieContainer.newKieSession("testWhen");
        Person person1 = new Person();
        person1.setClassName("三班");
        person1.setName("张三");
        person1.setAge(30);
        person1.setTeacher(new Teacher("王老师"));
        kieSession.insert(person1);

        Teacher teacher = new Teacher("李老师");
        kieSession.insert(teacher);

        kieSession.fireAllRules();
        kieSession.dispose();
    }
}

然后再Person类中新增私有属性private Teacher teacher

rule "from rule"
    when
        $p:Person($pt:teacher)
        $t:Teacher(teacherName == "王老师") from $pt
    then
        System.out.println("测试from");
end

上述规则文件内容也可以换成另外一种方式,其内容为:

rule "from rule2"
    when
        $p:Person()
        $t:Teacher(teacherName == "王老师") from $p.teacher
    then
        System.out.println("测试from2");
end

条件元素from支持对象源,返回一个对象集合,在这种情况下,from将会遍历集合中的所有对象,并分别匹配它们每一个对象值。

rule "from rule3"
    when
        $t:Teacher()
        $p:Person(className == "二班") from $t.personList
    then
        System.out.println("测试from3,person"+$p);
end
public class RulesWhen {
    public static void main(String[] args) {
        KieServices kieService = KieServices.Factory.get();
        KieContainer kieContainer = kieService.getKieClasspathContainer();
        KieSession kieSession = kieContainer.newKieSession("testWhen");
        Person person1 = new Person();
        person1.setClassName("一班");
        person1.setName("张三");
        person1.setAge(30);

        Person person2 = new Person();
        person2.setClassName("二班");
        person2.setName("李四");
        person2.setAge(20);

        Teacher teacher = new Teacher("李老师");
        person1.setTeacher(teacher);
        person2.setTeacher(teacher);

        List<Person> personList = new ArrayList<Person>();

        personList.add(person1);
        personList.add(person2);

        teacher.setPersonList(personList);

        kieSession.insert(person1);
        kieSession.insert(person2);
        kieSession.insert(teacher);

        kieSession.fireAllRules();
        kieSession.dispose();
    }
}

使用form时,设计者必须要注意使用属性功能,特别是与lock-on-active规则属性联合使用时,因为该属性可能产生不一样的结果。

条件元素collect

条件元素collect从组织结构上看,它需要结合from来使用,collect从字面意思来看是一个收集的功能,也就是说from是使用遍历的,而from collect是用来汇总的。而且在collect后的参数中还可以是匹配、遍历、收集、统计的功能,因此collect是一个十分强大的功能。

rule "collect rule1"
    when
        $al:ArrayList() from collect($p:Person(className=="一班"))
    then
        System.out.println("测试collect,person"+$al);
end

rule "collect rule2"
    when
        $al:ArrayList(size == 2) from collect($p:Person(className=="一班"))
    then
        System.out.println("测试collect,al size="+$al.size());
end
@Test
    public void testFromCollect(){
        KieServices kieService = KieServices.Factory.get();
        KieContainer kieContainer = kieService.getKieClasspathContainer();
        KieSession kieSession = kieContainer.newKieSession("testWhen");
        Person person1 = new Person();
        person1.setClassName("一班");
        person1.setName("张三");
        person1.setAge(30);

        Person person2 = new Person();
        person2.setClassName("二班");
        person2.setName("李四");
        person2.setAge(20);

        Person person3 = new Person();
        person3.setClassName("二班");
        person3.setName("王五");
        person3.setAge(25);

        Person person4 = new Person();
        person4.setClassName("一班");
        person4.setName("赵六");
        person4.setAge(18);


        kieSession.insert(person1);
        kieSession.insert(person2);
        kieSession.insert(person3);
        kieSession.insert(person4);

        kieSession.fireAllRules();
        kieSession.dispose();
    }

条件元素accumulate

条件元素accumulate是一个更为灵活的collect,它可以实现collect做不了的事。如条件元素accumulate的参数可以求一些不同的值,这是collect做不到的。

它主要做的事是允许规则迭代整个对象的集合,为每个元素定制执行动作,并在结束时返回一个结果对象,accumulate不仅支持预定义的累积函数的使用,而且可以使用其他内置函数,重要的是可使用自定义函数进行特殊化操作。常用的accumulate函数有求最大值、求最小值和求合等。

rule "accumulate rule1"
    when
        accumulate(Person($age:age),$min:min($age),$max:max($age),$sum:sum($age))
    then
        System.out.println("accumulate,min="+$min + ",max="+$max+",sum="+$sum);
end

输出结果

accumulate,min=18,max=30,sum=93.0

Drools附带有内置的accumulate功能,包括average(平均值)、min(最小值)、max(最大值)、count(统计)、sum(求和)、collectList(返回List)和collectSet(返回HastSet)。

第二种形式的accumulate只支持身后兼容,下面是inline的语法结构:

<result pattern>from accumulate(<source pattern>,init(<init code>),
action(<action code>),
reverse(<reverse code>),
result(<result expression>))

语法分析如下:

<init code>:init是做初始化用的,简单地说,在source pattern遍历完之后,就已经触发,类似for的开头。

<action code>:action会执行所有满足条件的源对象进行操作,类似for的方法体。在里面可写Java脚本。

<reverse code>:这是一个可选的被选方言的语义代码块,如果存在,将为不再匹配资源模式的每个资源对象的执行。这个代码块的目的是不在<action code> 块中做任何计算。所以,当一个资源对象被修改或删除时,引擎可能做递减计算,极大地提升了这些操作的性能。

<result expression>:返回值,是根据action上面两个遍历出来的结果进行一个返回,这个返回值中也可以进行计算。

<result pattern>:返回值类型,在<result expression>返回值的类型中再一次进行匹配,如果匹配不成功则返回false。编辑规则文件accumulate.drl,并添加“测试accumulatefrom第二种用法”规则,其内容为(注意加粗部分):

rule "accumulate rule2"
    when
        $total:Integer() from
        accumulate(Person($value:age),
                 init(Integer total = 0;),
                action(total += $value;),
                result(total)
        )
    then
        System.out.println("accumulate,$total="+$total);
end

init是初始化,action是遍历并计算,result是返回结果,这个规则可以返回类型,而且是在from前面定义的类型。上述例子中的返回值是total,它是一个Integer包装类,所以在from前面只能用Intger()来接收,当然要看返回值的具体类型,也可以返回String。但返回值类型必须有toString()才能正常返回。这里需要提醒设计者,设计的方言必须按语法进行规则的编码。

reverse是可选项,编辑Person.java文件添加“private Double dous;”并实现get set方法。新增规则

rule "accumulate rule3"
    when
        $total:Double() from
        accumulate(Person($age:age),
                 init(Double total = 0.0;),
                action(total += $age; System.out.println(total+">>>>>");),
                reverse(total-=$age; System.out.println(total+"<<<<<<");),
                result(total)
        )
    then
        System.out.println("accumulate,$total="+$total);
end

rule "accumulate rule4"
    when
        $ps:Person(dous>=3)
    then
        $ps.setDous(1.2);
        update($ps)
        System.out.println("dous:"+$ps.dous);
end
    @Test
    public void testAccumulate(){
        KieServices kieService = KieServices.Factory.get();
        KieContainer kieContainer = kieService.getKieClasspathContainer();
        KieSession kieSession = kieContainer.newKieSession("testWhen");
        Person person1 = new Person();
        person1.setClassName("一班");
        person1.setName("张三");
        person1.setDous(5.0);
        person1.setAge(30);

        Person person2 = new Person();
        person2.setClassName("二班");
        person2.setName("李四");
        person2.setAge(20);

        Person person3 = new Person();
        person3.setClassName("二班");
        person3.setName("王五");
        person3.setAge(25);

        Person person4 = new Person();
        person4.setClassName("一班");
        person4.setName("赵六");
        person4.setAge(18);


        kieSession.insert(person1);
        kieSession.insert(person2);
        kieSession.insert(person3);
        kieSession.insert(person4);

        kieSession.fireAllRules();
        kieSession.dispose();
    }
}

输出日志

18.0>>>>>
43.0>>>>>
63.0>>>>>
93.0>>>>>
accumulate,$total=93.0
dous:1.2
63.0<<<<<<
93.0>>>>>
accumulate,$total=93.0

针对上述的例子做以下总结:

  1. accumulate的使用,有一个很重要的函数action。这个函数提供了匹配源模式的执行动作。
  2. 将action看成两个状态,当源对象匹配源模式时,定会触发action,将触发过action的源对象称为有状态的(等同于标记),反之为无状态的。
  3. 当传入的源对象在RHS中或其他规则的RHS中发生改变(update,insert…)时,所有满足条件规则的将会再次被激活。
  4. 当规则再次被执行且遇到accumulate时,则有状态的源对象会先执行reverse函数进行“回滚”操作,并将修改后的源对象再次与accumulate条件进行比较,若比较为true,则该对象(被修改过的)会再次触发action函数;若比较为false,则不会执行action函数。
  5. 当规则再次被执行且遇到accumulate时,则无状态的源对象会与accumulate条件进行比较。若比较为true,则该对象(被修改过的)会第一次触发action函数;若比较为false,则不会执行action函数。

根据上述所有测试的结果得出以下结论。

  1. accumulate有3种形式,分别是inline、$min:min(XXX)min()

  2. inline形式虽然官方不推荐,但不代表不能使用。

  3. $min:min(XXX)只是其中的一种写法,如果在函数前面加了$XXX变量,则accumulate不是使用from方式,至于如何取$XXX变量的值,与模式方式是一样的。根据上面的介绍,是可以通过在函数的结束符“;”后面加条件约束,引用的方法也同模式方式一样。例如,在规则代码accumulate的第一种写法中加一个条件,其内容为:

    rule "测试 Accumulate 第一种 取对象中的最大值和最小值"
        when
    accumulate(Person($age:age),$min:min($age),$max:max($age),$sum:sum($age);)
        then
            System.out.println("传入的对象最小值为"+$min+"最大值为"+$max+"求合"+$sum);
    end
    
  4. min()写法,前面没有加任何的变量引用说明,当然也不可能在函数的结束符“;”后面再加条件约束,并且想要使用函数中返回的结果,在这种没有引用变量的前提下,则必须要引用from关键字,但from关键字有一个问题,即返回的结果。只有在使用函数时不能有变量引用,才能正常使用from。

  5. 使用accumulate语法时,如果source pattern结束符后面函数的结果有变量,那么不能使用from,且使用from返回结果必须是一个。上述说的其实都是accumulate中自带的一些函数和语法,accumulate的强大功能远不止如此。

    上述说的其实都是accumulate中自带的一些函数和语法,accumulate的强大功能远不止如此。accumulate函数可以自定义,实现一个新的accumulate函数,所以需要创建一个Java类,该类实现AccumulateFunction接口,在7.10版本中与官方说明不一样。规则使用自定义accumulate时,需要加关键字,如import accumulate com.rulesAccumulate.TestAccunmulateneeded factorial。getResult在自定义的类中可以写多个,同一个类可以写多个函数,并且可以对原来的方法进行修改。继承了AccumulateFunction接口相当于要重写里面的内置函数。

    内置的函数(总和、平均值等)是由引擎自动导入的,只有用户自定义定制的累积函数需要显式导入。新建TestAccunmulateneeded并实现AccumulateFunction目录为com/rulesAccumulate,其内容为:

    public class TestAccunmulateneeded implements AccumulateFunction {
    
    
        public static class Factorial implements Externalizable{
    
            public Factorial(){}
    
            public double total = 1;
    
            public void writeExternal(ObjectOutput out) throws IOException {
                System.out.println("writeExternal");
                out.writeDouble(total);
            }
    
            public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
                System.out.println("readExternal");
                total=in.readDouble();
            }
        }
    
    
        public Serializable createContext() {
            System.out.println("createContext");
            return new Factorial();
        }
    
        public void init(Serializable serializable) throws Exception {
            Factorial factorial = (Factorial) serializable;
            factorial.total = 1;
            System.out.println("init");
        }
    
        public void accumulate(Serializable serializable, Object o) {
            Factorial factorial = (Factorial) serializable;
            factorial.total = ((Number)o).doubleValue();
            System.out.println("accumulate");
        }
    
        public void reverse(Serializable serializable, Object o) throws Exception {
            Factorial factorial = (Factorial) serializable;
            factorial.total /= ((Number)o).doubleValue();
            System.out.println("reverse");
        }
    
        public Object getResult(Serializable serializable) throws Exception {
            Factorial factorial = (Factorial) serializable;
            Double d = factorial.total == 1 ? 1 : factorial.total;
            System.out.println("getResult");
            return d;
        }
    
        public boolean supportsReverse() {
            System.out.println("supportsReverse");
            return true;
        }
    
        public Class<?> getResultType() {
            System.out.println("getResultType");
            return Number.class;
        }
    
        public void writeExternal(ObjectOutput out) throws IOException {
            System.out.println("writeExternal father");
        }
    
        public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
            System.out.println("readExternal father");
        }
    }
    
    import com.domain.Person
    import accumulate com.accunmlate01.TestAccunmulateneeded factorial
    
    
    rule "isAccumulate"
        when
            accumulate(Person($value:age != null),$factorial:factorial($value))
        then
            System.out.println("自定义函数》》"+$factorial);
    end
    

    日志输出

    createContext
    supportsReverse
    init
    supportsReverse
    accumulate
    supportsReverse
    accumulate
    supportsReverse
    accumulate
    supportsReverse
    accumulate
    getResult
    自定义函数》》30.0
    

    通过上述操作,就可以实现自定义函数来操作特殊业务了。它的用法和sum、min内置函数相似,但是自定义的名称如果是sum、min等,是不会对原方法进行重写的。

条件元素规则继承

条件元素规则继承与Java相似。在学习Drools规则引擎的语法过程中,大部分操作都离不开使用Java脚本对一类fact对象进行操作,因为Drools规则引擎是基于Java开发的开源技术。编写Java代码时,如果发现有重复代码,一般的做法是写一些公共方法或通过继承的方式进行重构。Drools规则引擎的语法也存在这类条件继承的逻辑关系,其目的是为了减少相同代码的出现,从而方便代码的管理与优化。

一般写法

rule "extends1"
    when
        Person(name=="张三")
    then
        System.out.println("extends1");
end


rule "extends2"
    when
        Person(name=="张三")
        Person(age == 30)
    then
        System.out.println("extends2");
end

继承写法

rule "extends1"
    when
        Person(name=="张三")
    then
        System.out.println("extends1");
end


rule "extends2" extends "extends1"
    when
        Person(age == 30)
    then
        System.out.println("extends2");
end

条件元素do对应多then同条件

条件元素do对应多then同条件,实际项目中用到的地方并不多。规则继承允许避免重写条件设置多个规则,如使用do关键词可以有效地避免规则then部分出现多次相同的条件。它们基本上是用一个标识符来标记额外的子句,以制订一个规则体现为几个。确定规则应该指向定义的then部分。例如,如果想要写出规则继承中看到的两条规则作为一个单一规则,可以新建do.drl文件,目录为rules/isDoThen,其内容为:

rule "do then"
    when
        Person(age == 30)
        do[then01]
    then
        System.out.println("--------");
    then[then01]
        System.out.println("********");
end

日志

********
--------

从结果中看出,两个then都被输出了,因为在LHS部分中让其先执行then[then01]的RHS部分。do在RHS部分多了一个带“name”的then,这里就先称之为then的名称,然后正常insert实体Person到fact中,调用规则后,当条件满足时,都会被输出,只是先输出了“*****”。

使用do关键字标记可以去一个具体的后果,即then。在所有的条件规则都是true时,两个then都会执行,如果定义了两个不同的规则和规则继承一样,必须非常小心地使用这个特性。虽然小规模的业务使用do关键字可以节省很多重写的工作,但从长远来看,并不是非常合适,不仅无形中提高了规则的可读性,而且结果不可预测。

  1. 使用do时,do的参数与then的参数必须有对应关系。
  2. then可以不与do成对出现,如规则testDoNo3。
  3. 使用do时,do后的参数在RHS中是必须要存在的,参数是一个指针。
  4. 使用修改时,在非update情况下,then部分都会被执行。
  5. 使用修改时,在update情况下,then部分是有优先级的,是根据do的顺序控制的,但没有参数的then可能会改变其他的RHS部分。
  6. 使用do时,顺序控制是至关重要的。
  7. 无参数的then,优先级可能会低于指定的do。
  8. do只能在其他条件元素之后使用。
0

评论区