Drools4.0官方使用手册中文

第一章. Drools 4.0 发布版标注

1.1.  Drools 4.0的新内容

Drools4.0是从之前的Drools3.0.x系列以来的一次主要更新。在语言表达式,引擎性能和工具实用性方面都有一整套的新特性开发完成。下面列出一些最引人注意的更新列表。

1.1.1. 语言表达式增强

· 新的条件元素: from, collect, accumulate forall

· 新的字段约束操作: not matches, not contains, in, not in, memberOf, not memberOf

· 新的自索引字段: this

· 对条件元素嵌套的完全支持,对一阶逻辑的完全支持

· 支持使用&& ||连接多个约束条件

· 语法分析器的增强以去除之前的一些语言约束,如字符转码和关键字冲突

· 支持插件式语言,以及对MVEL脚本语言的完全支持

· 完全重写的DSL引擎,允许完全的本地化

· Fact属性对于返回值约束和内嵌求值的自动变换

· 支持嵌套访问,属性导航和简化的集合、数组以及映射的语法

· XML规则的增强支持

1.1.2. 核心引擎增强

· 对于元数据类型的本地支持,避免经常性的自动封包操作

· 支持透明的可选的影子Fact

· 对于复杂规则的Rete网络性能增强

· 支持规则流

· 支持有状态与无状态的Working Memory(规则引擎Session

· 支持异步Working Memory操作

· 规则代理(Agent)提供热部署机制和BRMS集成

· 对于规则冲突解决方案的动态salience

· 支持参数化查询

· 支持暂停命令

· 支持顺序执行模式

· 支持插件式的全局变量转换器

1.1.3. IDE 增强

· 支持调试中的规则断点

· 对于规则流的所见即所得功能

· 对于规则编制的新的向导编辑器

· 支持所有新的引擎特性

1.1.4. 业务规则管理系统 - BRMS

· 新的BRMS工具

· 具有Web2.0 Ajax特性的用户友好的Web界面

· Package配置

· 通过向导编辑器与文本编辑器,规则的作者更容易修改规则

· Package编译和部署

· 通过使用Rule Agent简化部署

· 通过分类组织规则,简化规则的查找

· 可版本化,你可以很容易的使用之前保存的版本替换现在的一套规则

· JCR兼容的规则仓库

1.1.5. 其它增强

· 减少了依赖类库和更少的内存占用

1.2. Drool3.0.xDrools4.0.x更新的提示

之前说到,Drools4.0是自Drools3.0.x系列以来的重要关键更新。但不幸的是为了达到这次发布的目标,带来了一些向后兼容性的问题,如邮件列表和博客中所提到的。

本节简单介绍一下如何从Drools3.0.xDrool4.0.x升级

1.2.1. API 变更

只有很少的API变更是对于常规用户可见并需要调整的。

1.2.1.1. Working Memory 创建

Drools3.0.x中只有一种Working Memory类型,它的是以有状态Working Memory的方式工作的。Drool4.0.x提供两个独立的有状态与无状态working memory,现在被称为Rule Session。在Drools3.0.x中建立Working Memory的代码是:

Example 1.1. Drools 3.0.x: Working Memory 创建

WorkingMemory wm = rulebase.newWorkingMemory();


Drools 4.0.x中必须变成:

Example 1.2. Drools 4.0.x: 有状态Rule Session创建

StatefulSession wm = rulebase.newStatefulSession();

StatefulSession对象与Drools3.0.x中的WorkingMemory对象的行为相同(它甚至继承了WorkingMemory接口),因此除了创建代码以外这个调整不会带来其它问题。

1.2.1.2. Working Memory 操作

Drools 4.0.x支持可插入的语言,并且已经内建了对JavaMVEL脚本语言的支持。为了避免关键字冲突,working memory的一些操作被重新命名如下:

Table 1.1. Working Memory Actions equivalent API methods

Drools 3.0.x

Drools 4.0.x

WorkingMemory.assertObject()

WorkingMemory.insert()

WorkingMemory.assertLogicalObject()

WorkingMemory.insertLogical()

WorkingMemory.modifyObject()

WorkingMemory.update()

 

1.2.2. 规则语言的变更

DRL规则语言也有如下一些变更是不支持向后兼容性的。

1.2.2.1. Working Memory Actions

Working Memory 操作在规则推论中的变化和在方法API上的变化相似,下表描述了这些改变:

Table 1.2. Working Memory Actions equivalent DRL commands

Drools 3.0.x

Drools 4.0.x

assert()

insert()

assertLogical()

insertLogical()

modify()

update()

 

1.2.2.2. 元数据类型支持与解包(unboxing)

Drools 3.0.x没有对元数据类型的本地支持,因此它会自动封包所有的元数据类型到各自的封装类中。这样的化,任何封包的变量绑定需要一个手工的解包操作。

Drools 4.0.x已经对元数据类型有完全的支持,不再需要封装任何值。因此所有之前的解包操作必须从DRL中删除。

Example 1.3. Drools 3.0.x 手工解包

rule "Primitive int manual unbox"

when

    $c : Cheese( $price : price )

then

    $c.setPrice( $price.intValue() * 2 )

end

 

上面的规则在4.0.x中将是:

Example 1.4. Drools 4.0.x 元数据支持

rule "Primitive support"

when

    $c : Cheese( $price : price )

then

    $c.setPrice( $price * 2 )

end

 

1.2.3. Drools 更新工具

Drools更新工具是一个帮助你将DRL3.0.x升级到4.0.x的一个简单的程序。

在这一点上它主要是更新working memory3.0.x  4.0.x的操作调用,但是期待它在未来的几个星期里可以覆盖更多的情况。要注意的是,这个工具并不是简单的进行字符替换,实际上它分析规则文件并确保不会做出预料之外的任何事情。因此它对于大量规则的升级来说是一个安全的工具。

Drools更新工具可以在下面的源码库链接中作为一个maven项目获取

http://anonsvn.labs.jboss.com/labs/jbossrules/trunk/experimental/drools-update/

你只需要Check Out它,然后与项目的pom.xml文件一起执行maven clean install 操作。在解决了所有依赖类库的class路径后,你可以使用下面的命令运行这个工具:

java -cp $CLASSPATH org.drools.tools.update.UpdateTool -f <filemask> [-d <basedir>] [-s <sufix>]

这个程序的参数非常容易理解:

· -h,--help, 显示一个非常简单的帮助

· -d 你的源码根路径

· -f  将要被更新的文件的查找模式。这个格式与ANT中使用的相同,*=单个文件,目录**=任何子目录级别;例如 src/main/resources/**/*.drl = 匹配所有在/src/main/resources下的子目录中的DRL文件

· -s,--suffix 被增加到所有更新文件上的后缀

2章. 规则引擎

2.1. 什么是规则引擎?

2.1.1. 背景介绍

A.I.Artificial Intelligence)是一个关注于“使计算机像人类一样思考“的广泛的研究领域,包括Neural Networks(神经网络), Genetic Algorithms(遗传算法), Decision Trees(决策树), Frame Systems(框架系统) and Expert Systems(专家系统)。Knowledge representation(知识呈现)是A.I.的一部分,关注于如何呈现和操纵知识。专家系统使用知识表示把知识编码简化成一个可用于推理的知识库──比如,我们可以用知识库处理数据以推出结论。专家系统又叫基于知识的系统、基于知识的专家系统,并被认为是A.I.的一个应用。开发一个专家库系统的过程被称为知识工程EMYCIN是最早的专家系统Shell(外壳)之一,它从医学诊断专家系统MYCIN发展而来。早期的专家系统有自己的logic hard coded "shells"(逻辑硬件编码外壳),把逻辑与系统相分离,为用户输入提供一个简单的使用环境。Drools是一个使用基于规则的方法实现的专家系统的规则引擎,更准确的说属于产生式规则系统。

术语生式规则从形式语法中产生,形式语法——使用一种抽象结构来准确描述形式语言 (wikipedia)

The term "Production Rule" originates from formal grammar - where it is described as "an abstract structure that describes a formal language precisely, i.e., a set of rules that mathematically delineates a (usually infinite) set of finite-length strings over a (usually finite) alphabet" (wikipedia).

业务规则管理系统在普通的规则引擎基础上通过提供—集中的业务用户,规则建立系统,管理,发布,协作,分析和终端用户工具等来达到更高的附加价值;使得企业能够以更顺利的方式引入规则引擎

规则引擎这个术语是非常不明确的,因为任何以任意形式使用能够应用于数据生成结果的规则的系统都可以称为规则引擎。包括像表单验证和动态表达式引擎这样的简单系统都可以称之为规则引擎。作者Malcolm Chisholm 的著作《How to Build a Business Rules Engine (2004)》就例证了这种不明确性。该书实际是讲述如何为管理校验规则而建立和维护一个数据库计划。该书中接着展示了如何根据这些校验规则产生VB代码来校验输入—在某些情况下这是很有用的,作者十分惊讶于这种不明确性,在没有觉察到各种规则引擎间细微不同之处时,他希望能发现其中的奥秘以帮助增强Drools引擎。JBoss jBPM(业务流程管理工具)在它的判断节点上使用表达式和代理引用来控制工作流中的事务。通过在每个节点上求值以决定分支的流向—这同样是一个规则引擎。产生式规则系统既是一种规则引擎,又是一个专家系统,而之前提到的校验器和表达式求值规则引擎不是专家系统

产生式规则系统完全关注于精确表达propositional(命题)和first order logic(一阶逻辑)的知识表示,不存在含糊不清的定义。产生式规则系统的核心是一个能够处理大量规则和事实的推理引擎。推理引擎将事实、数据与产生式规则(也可以叫做产生式,或干脆叫规则)进行匹配,以推出结论。产生式规则是一个用一阶逻辑进行知识呈现的二元结构。

when

    <conditions>

then

    <actions>

将新的或已存在的事实与产生式规则进行匹配的过程被称为模式匹配,这个过程由推理机完成。推理机使用的用于模式匹配的算法有很多,包括:

· Linear 线性的

· Rete    网状

· Treat  

· Leaps  叶状

Drool实现了ReteLeaps算法;Leaps是试验性质的,因为它是个十分新的算法。Drools中的Rete算法被称为ReteOO,表示Drools为面向对象系统(Object Oriented systems)增强并优化了Rete算法。其它基于Rete算法的引擎也有他们对Rete算法进行独有增强后的市场术语,比如RetePlusReteIII。要知道象ReteIII这样的名字纯粹是市场性的,不像原始版本的Rete算法,它们没有公布实现细节,这一点很重要;因此,问类似于“Drools是用ReteIII实现的吗?”的问题是没有任何意义的。最寻常的对Rete算法的增强在下面这篇文章中有论述"Production Matching for Large Learning Systems (Rete/UL)" (1995) by Robert B. Doorenbos

规则保存在Production Memory(规则库)中,推理机要匹配的facts(事实)保存在Working Memory(工作内存)中。事实被插入到工作内存中后,可能被修改或删除。一个有大量规则和事实的系统可能会很多规则被满足,这些规则被称为具有冲突性。Agenda通过(冲突决策策略)管理这些冲突规则的执行顺序。

 

Figure 2.1. 基础Rete网络

 

产生式规则系统的推理机是有状态的并且能够维持其中状态值的准确性,称为Truth Maintence(真值维护)。规则引擎中的动作仅当其依赖的约束为真值情况下执行,如果约束不再有效,则推论动作不会执行。"Honest Politician"Drools提供的一个示例)就是这种真值维护的示范,在示例中规定,只要在存在诚实的政治家时,一个政体才有希望。

 

when

    an honest Politician exists

then

    logically assert Hope

 

when

   Hope exists

then

   print "Hurrah!!! Democracy Lives"

 

when

   Hope does not exist

then

   print "Democracy is Doomed"

产生式规则系统有两种执行方法──正向推理和逆向推理,两种方法都使用的系统称为混合型产生式规则系统。理解这两种操作方法是理解产生式规则系统之所以不同和怎样从中选择最合适的系统的关键。正向推理是数据驱动的,facts事实被传递到工作空间中,在那里有一个或多个规则与这些事实匹配,并由Agenda安排执行—我们从一个事实开始,传递事实,最后得到一个结论。Drools是基于正向推理的规则引擎。

Figure 2.2.  正向推理

 

逆向推理是由目标驱动的,这意味着我们从一个引擎需要满足的结论开始进行推理。在这个结论不能满足时,将搜索一些能够满足的结论来推理,称为子目标,这些子目标将帮助完成当前目标的某些未知部分——引擎持续这个过程,直到最初的结论被证明或没有可证明的子目标。Prolog是逆向驱动型的引擎。Drools将在下一个主要版本中加入对逆向推理的支持。

Figure 2.3. 逆向推理

 

2.2. 为何使用规则引擎?

人们常常会问到:

1. 何时应当使用规则引擎?

2. 规则引擎与"if...then" 这样的硬编码比起来有什么优点?

3. 为何应当使用规则引擎取代脚本框架?

我们尝试通过以下说明来解答这些问题

2.2.1. 规则引擎的优点

· 声明式编程

规则引擎允许你描述做什么而不是如何去做。

这里的主要优点是使用规则更加容易对复杂的问题进行表述,并得到验证。 (规则比编码更容易阅读).

规则系统能够解决非常非常困难的问题,并提供了方案怎样达到和在解决问题的方向上所作的每一个决定的原因(这对于类似神经网络这样的AI系统来说不容易达到)

· 逻辑与数据分离

数据保存在系统对象中,逻辑保存在规则中。这根本性的打破了面向对象系统中将数据和逻辑耦合起来的局面,这点是有利的也是不利的,在于你的观察角度。这样做的结果是,将来逻辑发生改变时更容易被维护,因为逻辑保存在规则中。这点在逻辑是跨领域或多领域中使用时尤其有用。通过将逻辑集中在一个或数个清晰的规则文件中,取代了之前分散在代码中的局面。

· 速度及可测量性

Rete算法、Leaps算法,以及由此衍生出来的DroolsReteLeaps算法,提供了对系统数据对象非常有效率的匹配。这些都是高效率尤其当你的数据是不完全的改变(规则引擎能够记得之前的匹配)。这些算法经过了大量实际考验的证明。

· 知识集中化

通过使用规则,将建立一个可执行的规则库。这意味着规则库代表着现实中的业务策略的唯一对应,理想情况下可读性高的规则还可以被当作文档使用。

· 工具集成

例如Eclipse(将来可能在基于Web的界面上)这样的工具为规则的修改与管理、即时获得反馈、内容验证与修补提供了办法。审查与调试工具同样也可用了。

· 解释机制

通过将规则引擎的决断与决断的原因一起记录下来,规则系统提供了很好的“解释机制”。

· 易懂的规则

通过建立对象模型以及DSL域定义语言),你可以用接近自然语言的方式来编写规则。这让非技术人员与领域专家可以用他们自己的逻辑来理解规则(因为程序的迷宫已经被隐藏起来了) 

2.2.2. 何时应当使用规则引擎?

对这个问题最简短的回答就是“当没有令人满意的传统的程序设计方法能够解决这个问题时”。下面上对所谓没有传统解决方法的一个描述: 

· 对于传统代码来说,问题需要的精确度太高。

这种问题可能并不复杂,但是你找不到一种稳定的方法去建立它。

· 问题超越了任何有明显运算法则的方案。 

它是一个难以解决的复杂问题,没有明显的传统解决方案或者问题没有一个准确的定论。

· 业务逻辑经常发生改变

逻辑本身是简单的(但不是指过于简单),但是规则经常发生变化。在许多软件组织中正式版本的间隔是较长并且较少的,规则可以在适当的安全前提下帮助提供一定的敏捷性。

· 领域专家(或者业务分析师)是非技术人员

领域专家通常对业务规则和流程具有很好的认知。他们通常是不了解软件技术的人员,但是具有很好的逻辑性。规则能够让他们用自己的术语来描述业务逻辑。当然他们仍然需要严密的思考和良好的逻辑思维能力(许多在非软件技术型岗位上的人没有进行过形式逻辑的训练,因此在和他们工作时要特别小心,在将业务知识编撰成规则时,要特别注意业务规则和流程应当是当前能够理解的)。

如果规则对于你的项目组来说是一种新的技术,那在使用前必须将学习与管理的费用成本考虑进去。规则不是一种无意义的技术,这篇文档尽量让其易于理解。

在一个面向对象的应用中,规则引擎通常被用在包含业务逻辑的关键部分(具体与应用相关),特别是在十分繁杂凌乱的部分。这是对面向对象中将所有逻辑封装在对象中的一个倒置。但这并不是说应该抛弃对象模型,相反的来说在任何一个现实应用中业务逻辑仅仅是应用的一部分。如果你曾注意到在你的代码中有很多”if””else””switch”和其它凌乱的逻辑,你总是要回过头去修改它们(可能是由于提供给你的逻辑是错误的,或是你的理解变化了),那么可以考虑使用规则。如果你所面对的问题没有算法或者模式合适解决,考虑使用规则。

规则可以被嵌入你的应用中,或者作为一个服务使用。通常规则最好被当作一个有状态的组件使用——因此它们通常在应用中是一个整体。无论怎样,在一些规则被成功定义为可重用的服务的个案中,规则是无状态的。

如果考虑在组织中使用规则,那考虑产品中将如何使用和更新规则是很重要的(选择很多,但是不同的组织间有不同的需要——通常这超出了应用者/项目团队的控制)

 

2.2.3. 何时不要使用规则

这里引用Drools邮件发送清单中的话(Dave Hamu):“在我看来,在使用规则引擎的兴奋中,人们忘记了规则引擎只是一个复杂的应用或方法中的一部分。实际上,规则引擎不是用于规则的工作流引擎或进程管理工具。对特定的工作要使用恰当的工具。当然,必要时老虎钳可以当作锤子用,但那并不是发明老虎钳的本意。”

因为规则引擎是动态的 (动态的在这里意味着规则可以象数据一样保存、管理和更新),它们通常被看作发布软件系统的一种解决方案(大多数IT部门似乎存在的目的是防止软件系统遗弃)。如果这是你希望使用规则引擎的原因,应当意识到在可以写出公开发布的规则时,规则引擎能够以最佳方式工作。做为另一个方面,你也可以考虑使用数据驱动的设计(查找表)或者脚本/流程引擎带有能够在数据库中管理并能够动态更新的脚本。

2.2.4. 脚本或流程引擎

希望之前的讨论能够让你明白何时应该使用一个规则引擎。

另一方面,基于脚本的引擎也提供了"动态重组"的能力。 

另外流程引擎 (通常为工作流)jBPM允许你使用绘图(或编程)方式描述过程的步骤——这些步骤也能够带有包含简单规则的策略决策点。过程引擎与规则通常能够很好的在一起工作,因此这不是一个二选一的要求。

对于规则引擎的一个关键点是一些规则引擎实际上就是脚本引擎。在脚本引擎下是你的程序被牢牢绑定在脚本上(如果那里有规则,则必须直接调用),这导致未来维护更加困难,因为它们趋向于越来越复杂。在脚本引擎之上的好处是能够很容易的开始实现规则并迅速获得反馈(概念上会使得命令式编程更简单) 

许多人在过去已经成功实现了数据驱动的系统(通过控制表中保存的元数据来影响应用的行为)——保持在一定控制范围内时这能够很好的工作。但是当超过了允许范围(如只有建立者能够改变应用的行为)太多这将很快失控或者导致应用停滞不前。

2.2.5.  紧密耦合与松散耦合

无疑在系统设计中你听说过紧密耦合与松散耦合。通常人们认为在设计中松散耦合是更好的,因为增加了系统灵活性。同样你可以有紧密耦合与松散耦合两种规则。紧密耦合在这里意味着一个规则的执行后将导致另一个规则有一个明显的结果,换句话说就是在规则之间有清晰的逻辑推理关系。如果你的规则都是紧密耦合的,那这些规则在将来会缺乏灵活性,并且更明显,这样规则引擎可能是使用过度了(因为逻辑是一组清晰的规则推理——能够被直接编码[一个决策树可能更适宜])。这并不是说紧密耦合或松散耦合本质就不好,但是在你考虑使用规则引擎并且捕捉规则时应当牢记这点。松散耦合使得系统中的规则在变更,删除和新增时不会影响其它无关的规则。 

2.3. 知识表征

2.3.1. 一阶逻辑

规则是由一阶逻辑或断言逻辑编写而成,是由命题逻辑扩展而来。Emil Leon Post(数学家与逻辑学家 1897-1954)是第一个制定利用符号来表示逻辑推理系统——作为一个推论,他证明了任何逻辑系统(包括数学)能够被这样的一个符号系统来表达。

一个命题就是一项能够被归纳为TrueFlase的一个声明。如果事实能够被一个独立的声明证明就称之为“封闭声明”。在程序中这表现为一个不含任何变量的表达式:

10 == 2 * 5

包含了一个或多个变量或Fact的表达式称为“开放声明”,在这种表达式中,只有当一个变量的实例被放入表达式后,才能知道该表达式是否为真值:

Person.sex == "male"

SQL语句中,一条语句的执行结果通常是将匹配的结果返回:

select * from People where People.sex == "male"

所有由以上SQL返回回来的数据都会推论为是男性的数据,下图显示SQL如何作为一个推理引擎从People表中返回我们需要的结果。

Figure 2.4.  SQL 作为一个单纯的推理引擎

 

因此在Java,我们可以认为一个简单命题的形式“变量”“运算符”“值”——在这里值通常代表一个字符串——命题可以看作是一个字段约束。命题之间可以使用“&&”或“||”进行逻辑合并。如下例:

person.getEyeColor().equals("blue") || person.getEyeColor().equals("green")

这在规则引擎中也可以用||”逻辑运算符来表示。

Person( eyeColour == "blue" ) || Person( eyeColor == "green" )

 “||”逻辑运算也可以直接作用于字段约束上(Drools4.0M2实现该功能)

Person( eyeColour == "blue"||"green" )

命题逻辑并不能表达数据结构的标准(不具有图灵完备性),这约束了其可以定义的问题。一阶逻辑或断言逻辑通过两种新的量词概念以允许表达式定义结构——具体来说是全称量词和存在量词,从而扩展了命题逻辑。全称量词允许对每一个对象检查某些情况是否为真,通常使用“forall”这个条件元来支持(在Drools4.0M2中实现)。存在量词检查对象的存在性,这意味着该对象至少要出现一次——由“not”和“exist”这两个条件元支持。

设想我们定义了两个类——StudentModuleModule代表了一个学生在每个学期里要上的所有课程,通过List集合引用。在学期末,每个课程有一个得分。如果某个学生的所有课程中有一门低于40分代表该学生本学期不合格——通过存在量词可以使用“less then 40” 这样的开放命题来检查各Module中是否有符合该条件的情况。

    

    public class Student {

    private String name;

    private List modules;

    ...

    }

    public class Module {

    private String name;

    private String studentName;

    private int score;

Java具有图灵完备性,因此你可以通过编写代码遍历数据结构来检查存在性。下面的代码用来返回所有学期不合格的学生列表。

    List failedStudents = new ArrayList();

    for ( Iterator studentIter = students.iterator(); studentIter.hasNext() {

        Student student = ( Student ) studentIter.next();

        for ( Iterator it = student.getModules.iterator(); it.hasNext(); ) {

            Module module = ( Module ) it.next();

            if ( module.getScore() < 40  ) {

                failedStudents.add( student ) ;

                break;

            }

        }

    }

早期的SQL实现不是图灵完备的,因此不能提供对所存取的数据结构的量化性。目前的SQL引擎通过对嵌套SQL语句进行“exist”和“in”的关键字构造来支持存在性。以下代码显示如何通过SQL和规则来获取学期不合格的学生。

select

    *

from

    Students s

where exists (  

    select

        *

    from

        Modules m

    where

        m.student_name = s.name and

        m.score < 40

)

rule "Failed_Students"

    when

        exists( $student : Student() && Module( student == $student, score < 40 ) )

2.4. Rete 算法

RETE算法是由Charles Forgy博士发明的,发表在他的博士论文中(1978-79)。这篇文章的简化版本发布在1982(http://citeseer.ist.psu.edu/context/505087/0) 单词Rete是拉丁语中的网的意思,代表网络的概念。Rete算法可以被分为两个部分:规则编辑和允许环境中的执行。

编辑算法描述了在生产空间(Production Memory)的规则如何产生一个有效的鉴别网络。用通俗的话来说,有效的鉴别网络是用来过滤数据的。办法是对在网络间传递的数据进行过滤。在网络顶部时节点将匹配许多数据,但是到了网络底部是将匹配较少的数据。在网络的很底部是终端节点。在Frogy博士1982年的文章中,他描述了四种基本的节点:root1-input2-inputterminal

Figure 2.5. Rete Nodes

 

所有对象通过Root节点进入网络。从那之后立刻进入ObjectTypeNode节点。ObjectTypeNode节点的目的是保证引起不会做超过它需要做的工作范围。例如有两个对象:AccountOrder。如果规则引擎尝试在每一个节点评估所有对象,这将花费很多循环。为了让工作更有效率,规则引擎应当只将与节点匹配的对象类型传入节点。最初是建立一个ObjectTypeNode节点,所有1-input2-input节点都跟在后面。通过这种方法,应用传进来的Account对象不会被传送到需要Order对象的节点上。在Drools中,当一个对象被设置后,它将通过对象的类在HashMap中查找有效的ObjectTypesNode节点列表,如果这个列表不存在,它会搜索所有的ObjectTypesNode节点,找到有效的匹配并缓存到列表中。这能够让Drools通过一个instanceof检查来匹配任何对象类型。

Figure 2.6. ObjectTypeNodes

ObjectTypdeNode节点能够向AlphaNodeLeftInputAdapteNodeBetaNode节点传播数据。AlphaNode节点用来对字符串进行求值。虽然在1982年的论文中只提到了等式操作,但许多Rete算法的实现支持其它操作。例如,Account.name==”Mr Trout”是一个字符串等式。当规则对一个对象类型有很多字符串等式时,它们会链接在一起。这意味着如果应用设置了一个Account对象,它必须满足了一个字符串等式后才能传递到下一个AlphaNode节点。在Forgy博士的论文中,他称之为内在条件。下面显示了针对Cheese对象的结合AlphaNode节点情况:Cheese( name == "cheddar, strength == "strong" ):

Figure 2.7. AlphaNodes

Drools 通过在ObjectTypdeNode 节点向AlphaNode节点传递时使用Hash操作优化了Rete算法。每次一个AlphaNode节点被加入到ObjectTypdeNode 节点时,将在HashMap中增加一个以字符串为键,AlphaNode节点为值的键值对。当一个新的对象实例进入ObjectTypdeNode节点时,它可以直接从HashMap中返回正确的AlphaNode节点,避免了多余的字符串检查。

有两种two-input节点——JoinNodeNotNode,都是BetaNode的类型。BetaNode节点用于比较两个对象和它们的字段。两个对象可能是相同或不同的类型。我们将这两个输入称为左和右。BetaNode的左输入通常是一组对象的列表,在Drools中称为语义(Tuple)。右边的输入是单个的对象。这两者能够使用“exist”进行对比检查。BetaNode具有记忆功能。左边的输入被称为Beta Memory,会记住所有到达过的语义。右边的输入成为Alpha Memory,会记住所有到达过的对象。Drools通过在BetaNode节点上建立索引扩展了Rete算法。例如,如果我们知道一个BetaNode用来对一个字符型字段进行检查,对每一个进入的对象,我们能够使用那个字符串的值进行一个Hash查找。这意味着当Fact从相反的一边进入BetaNode时,不需要再对所有Fact进行检索以发现有效链接,只要一个查找就可以返回潜在的有效候选。在任何时候,一个有效的链接是发现语义与对象的链接,它指的是一个局部匹配,并传播到下一个节点。

Figure 2.8. JoinNode

 

为了让最初的对象,在上图中是Cheese,进入Rete网络,我们使用了一个LeftInputNodeAdapter——将一个对象作为输入并传播一个对象的语义。

终端节点被用来表示一个单独的规则已经匹配了它的所有条件——在这一点上,我们说这个规则实现完全的匹配。一个使用“or”链接符的规则在每一个可能的逻辑路径上产生子规则,在这种情况下可以有多个终端节点。

Drools使用了节点共享技术。许多规则重复着同样的模式,节点共享允许我们删除这些模式的多余复制,从而避免对每一个单独得实例进行重复的评估。下面两个规则在一开始共享了相同的模式,但后面部分不同:

    

    rule

    when

        Cheese( $chedddar : name == "cheddar" )

        $person : Person( favouriteCheese == $cheddar )

    then

        System.out.println( $person.getName() + " likes cheddar" );

    end

    

    rule

    when

        Cheese( $chedddar : name == "cheddar" )

        $person : Person( favouriteCheese != $cheddar )

    then

        System.out.println( $person.getName() + " does not like cheddar" );

    end

你可以在下图看到,在编译的Rete网络中Alpha节点被共享了,但是Beta节点没有共享。每一个Beta节点有自己的终端节点。如果第二个模式是相同的也会被共享。

Figure 2.9. Node Sharing

 

2.5. Drools 规则引擎

2.5.1. 概述

Drools被分为两个主要的部分:编制和运行时

编制的过程包括为规则建立DRLXML文件,传入一个由Antlr 3文法器定义的解析器中。[下面的翻译要参考图1.10理解]解析器对文件中规则文法的正确性进行检查并为descr建立一个中间结构,在AST中的descr代表规则的描述。AST然后将descr传入Package Builder中,由其进行打包。Package Builder同时负责包括打包中用到的所有代码产生器和编译器。

Package对象是自包含并可配置的,它是一个包含规则的序列化的对象。

Figure 2.10. 编制组件

RuleBase是运行时组件,包含一个或多个PackagePackage在任何时候都可以向RuleBase中添加或删除。

一个RuleBase可以同时初始化多个Working Memory,在其间维护着一个弱引用,除非重新进行配置。Working Memory包含许多子组件,如Working Memory Event Support(事件支持), Truth Maintenance System(真值维护系统), Agenda  Agenda Event Support(事件支持)。向Working Memory中设置对象的工作可能要在建立了一个或多个激活的规则后才结束。Agenda负有规划激活规则运行的责任。

Figure 2.11. 运行时组件

 

2.5.2. 编制

Figure 2.12. PackageBuilder

 

DrlParser, XmlParser  PackageBuilder这三个类被用来进行编制。两个分析类用来从提供的可读流实例中建立descr AST模型。PackageBuilder提供相应API使得你可以不用记住这些类。"addPackageFromDrl" "addPackageFromXml"是两个常用方法——都要求一个可读流实例作参数。下面的例子说明一个如何建立一个包括XMLDRL规则文件的包,文件的位置在相对于classpath变量中定义的路径下。注意,对于增加到当前PackageBuilder实例中的所有规则包,必须具有同样的包名称空间。

Example 2.1. 从多个源中建立包

PackageBuilder builder = new PackageBuilder();

builder.addPackageFromDrl( new InputStreamReader( getClass().getResourceAsStream( "package1.drl" ) ) );

builder.addPackageFromXml( new InputStreamReader( getClass().getResourceAsStream( "package2.xml" ) ) );

builder.addRuleFlow( new InputStreamReader( getClass().getResourceAsStream( "ruleflow.rfm" ) ) );

Package pkg = builder.getPackage();      

有一个要点,在正式使用PackageBuilder之前,要先检查其中是否有错误。当一个被打断的Package加入ruleBase时,将会抛出一个InvalidRulePackage,错误被包含在这个Package并且只有一个等价于toString()的方法有效。如果你询问PackageBuilder,可以返回更多的有效信息。

Example 2.2. 使用 PackageBuilder 检查错误

PackageBuilder builder = new PackageBuilder();

builder.addPackageFromDrl( new InputStreamReader( getClass().getResourceAsStream( "package1.drl" ) ) );

PackageBuilderErrors errors = builder.getErrors();

PackagBuilder使用PackageBuilderConfiguration类配置。

Figure 2.13. PackageBuilderConfiguration

PackageBuilderConfiguration的默认值可以通过程序设置或通过外部特性配置在一开始就改变。在配置之前先通过ChainedProperties类在许多位置搜索drools.packagebuilder.conf文件;当发现它们之后,加入其中定义的配置到主要特性列表中;这提供了一个搜索的级别优先性。位置优先性的顺序是:系统特性,用户在系统特性中定义的文件,用户Home目录,工作目录,各种META-INF位置。除此之外,droosl-compiler jar在它的META-INF目录中有它自己的默认设置。

当前PackageBulderConfiguration处理Accumulate函数的注册,语言的注册以及主要的类调用器(ClassLoader)。

Drools有一个可插入的语言系统,它允许其它语言编译和执行表达式与代码块,当前两种支持的语言是JavaMVEL。每一个都有它自己的DialectConfiguration接口实现;Javadocs文档中提供了每一个setter/getter的细节以及用于配置它们的特性名称。

Figure 2.14. JavaDialectConfiguration

JavaDialectConfiguration允许设置用于支持它的编译器和语言水平。你可以通过在可以被ChainedProperties 实例发现的packagebuilder.conf文件中设置"drools.dialect.java.compiler"特性来覆盖它,或者你可以通过下面的程序在运行时改变。

Example 2.3. 配置JavaDialectConfiguration 使用JANINO

PackageBuilderConfiguration cfg = new PackageBuilderConfiguration( );

JavaDialectConfiguration javaConf = (JavaDialectConfiguration) cfg.getDialectConfiguration( "java" );

javaConf.setCompiler( JavaDialectConfiguration.JANINO );            

如果在你的classpath中没有Eclipse JDT Core,你必须在实例化PackageBuilder之前覆盖这个默认的编译器设置,你可以通过一个ChainedProperties类可以发现的packagebuilder特性文件或通过下面的程序方式改变它;注意我使用特性为启动注入值的时间。

Example 2.4. 配置 JavaDialectConfiguration 使用 JANINO

Properties properties = new Properties();

properties.setProperty( "drools.dialect.java.compiler",

                        "JANINO" );

PackageBuilderConfiguration cfg = new PackageBuilderConfiguration( properties );

JavaDialectConfiguration javaConf = (JavaDialectConfiguration) cfg.getDialectConfiguration( "java" );

assertEquals( JavaDialectConfiguration.JANINO,

              javaConf.getCompiler() ); // 确认编译器被正确配置            

当前允许在JaninoEclipse JDT两个编译器之间选择一个,以及设置不同的JDK源码级别("1.4" "1.5")和一个父类装载器。默认的编译器是在JDK1.4水平上的Eclipse JDT Core编译器,以及将设置到"Thread.currentThread().getContextClassLoader()"中的父类装载器。

接下来的代码显示如何指定JANINO编译器:

Example 2.5. 通过特性配置PackageBuilder使用JANINO

PackageBuilderConfiguration conf = new PackageBuilderConfiguration();

conf.setCompiler( PackageBuilderConfiguration.JANINO );

PackageBuilder builder = new PackageBuilder( conf );

MVELDialectConfiguration更加简单,并且只允许精确的(strict)的模式被打开或关闭,默认stricttrue;这意味着所有方法调用必须是类型安全的,不管是经过推断声明或显示声明的类型。

Figure 2.15. MvelDialectConfiguration

 

2.5.3. RuleBase

Figure 2.16. RuleBaseFactory

RuleBase使用RuleBaseFactory实例化,默认情况下返回一个ReteOORuleBasePackage通过使用addPackage方法按顺序加入。你可以指定任何名称空间的Packages或者同一名称的多个包加入RuleBase

Example 2.6. 增加Package到新的RuleBase

RuleBase ruleBase  = RuleBaseFactory.newRuleBase();

ruleBase.addPackage( pkg  );        

 

Figure 2.17. RuleBase

 

RuleBase包含一个或多个规则包,它们已经被校验和编译完成,以备使用。RuleBase是可序列化的,因此它可以被配置到JNDI或其它类似的服务上。典型的,RuleBase在第一次使用时产生并缓存,这是一个昂贵的操作(花费大量时间)。RuleBase通过RuleBaseFactory来初始化,默认情况下返回一个ReteOO RuleBase(面向对象化的Rete算法的RuleBase)。可以指定参数决定返回ReteOO还是Leaps。接着使用addPackage方法将包加入。你可以指定在任意名称空间中的包,并且处于相同名称空间的多个包可以被加入。

RuleBase的实例是线程安全的,这意味着你可以在你的应用间共享一个实例,现实中可能是一个Web应用程序。在RuleBase上最常见的操作是建立一个新的规则Session,可以是有状态或无状态的。

RuleBase也保存着任何由它产生的有状态Session的引用,因此如果规则改变(被新增/删除等等。对于长时间运行的Session),它们可以用最新的规则更新而不需要重启Session。你可以指定不去维护一个引用,但只在你明确知道Rule Base不会被更新的情况下使用。RuleBase不会对无状态Session保持引用。

ruleBase.newStatefulSession();  // maintains a reference.

ruleBase.newStatefulSession( false ); // do not maintain a reference    

Package可以在任何时候新增或删除——所有的变更都会传播到现存的有状态Session中;不要忘记调用fireAllRules()激发已经激活的规则。

ruleBase.addPackage( pkg );  // 新增package实例

ruleBase.removePackage( "org.com.sample" ); //通过名称空间删除package和所有部件

ruleBase.removeRule( "org.com.sample", "my rule" ); // 从名称空间删除指定规则 

有方法可以用来删除单独的规则,但是没有方法新增一个单独的规则——要实现这个目标,增加一个只含有一个规则的Package

RuleBaseConfigurator可以被用来指定RuleBase的附加行为。RuleBaseConfigurator在加入Rule Base中之后被设置为不可改变。几乎引擎的所有优化方式都可以在这里打开或关闭,也可以设置要执行的行为。用户通常关心插入行为(同一性和等同性)以及叉集行为(删除或保持同一性相等的叉集)。

RuleBaseConfiguration conf = new RuleBaseConfiguration();

conf.setAssertBehaviour( AssertBehaviour.IDENTITY );

conf.setRemoveIdentities( true );

RuleBase ruleBase = RuleBaseFactory.newRuleBase( conf );

Figure 2.18. RuleBaseConfiguration

 

2.5.4. WorkingMemory 和有状态/无状态Sessions

Figure 2.19. WorkingMemory

WorkingMemory保存所有插入的数据的引用直到该数据被删除,并且它是与你的应用程序发生交互的地方。WorkingMemory是有状态的对象,它们可以短期或长期存在。

2.5.4.1. Facts

Facts是由你的应用程序设置到Working Memory 中的对象。Facts可以是任何规则可以存取的Java对象。规则引擎完全不会克隆对象,它仅仅是保存对对象的一个引用/指针。Facts是你的应用程序的数据。字符串以及其它没有gettersetter方法的类不是有效的Facts,不能被用于字段约束,因为字段约束依赖于根据JavaBean标准的gettersetter方法来与对象交互。

2.5.4.2. Insertion

" Insert "是通知working memory关于facts的操作。例如,WorkingMemory. insert (yourObject)。当你插入一个fact时,它检查与规则的匹配情况。这意味着所有工作在插入Fact期间就已经完成,但在你调用"fireAllRules()"方法之前没有任何规则被执行。在你完成所有fact的设置之前你不应该调用"fireAllRules()"。人们通常会误会所有的相关工作在"fireAllRules()"调用后才进行。专家系统通常使用术语"assert""assertion"来指代将fact设置到系统中的操作,但是随着assert在大多数语言中成为了一个关键字,我们使用Insert关键字来代替它,以避免关键字冲突。

 

当对象被InsertWorkingMemory中时,它返回一个FactHandle。这个FactHandle是象征着你插入WorkingMemory中的对象,它也是当你希望从Working Memory中删除或修改一个对象时所必须的。

Cheese stilton = new Cheese("stilton");

FactHandle stiltonHandle = session.insert( stilton );      

 

RuleBaseWorking Memory在插入时可能有两种操作方式——等同性equality和同一性identityidentity是默认操作。

 

Identity意味着Working Memory使用一个IdentityHashMap去保存所有被插入进来的对象。被插入进来的新的实例都会返回一个新的FactHandle。如果一个实例被插入进来两次,将返回前一个的FactHandle,对相同实例直接忽略第二次插入。

Equality意味着Working Memory使用一个HashMap保存所有插入的对象。这个模式下,当插入的 Object 相等时,它返回同一个 FactHandle

2.5.4.3. Retraction

当你 retract 一个 fact  WorkingMemory 将不再跟踪那个 fact 。任何被 activated 并依赖那个 fact 的规则将被取消。注意:完全有可能存在某条规则是依赖于一个 fact “不存在”( non existence )。在这种情况下, retract 一个 fact 将导致一条规则被激活。对一个 Fact 进行 Retraction ,必须用 Insert时返回的那个 FactHandle 做为参数。

Cheese stilton = new Cheese("stilton");

FactHandle stiltonHandle = session.insert( stilton );

....

session.retract( stiltonHandle );            

2.5.4.4. Update

当一个 Fact 被修改了,会通知规则引擎进行重新处理。在规则引擎内部实际上是对旧的 Fact 进行 retract ,然后对新的 Object 再进行 insert。要使用 modifyObject() 方法来通知 Working Memory ,被改变的 Object 并不会自己通知规则引擎。注意: modifyObject() 方法总是要把被修改的 Object 做为第二参数,这就允许你把一个不可变对象替换为另一个新对象。update()方法只能在影子代理打开的情况下对对象使用。如果你没有使用影子代理,那你必须在调用update之前先session. modifyRestract (),然后在update之后调用session.modifyInsert()

Cheese stilton = new Cheese("stilton");

FactHandle stiltonHandle = workingMemory.insert( stilton );

....

stilton.setPrice( 100 );

workingMemory.update( stiltonHandle, stilton );              

2.5.4.5. 全局变量

Global 是一个能够被传进 WorkingMemory 但不需要进行insert操作的命名对象。大多数这些对象被用来作为静态信息或被用在一条规则的RHS 部分的服务,或者可能是从规则引擎返回对象的一种方法。 如果你在规则的LHS部分使用全局变量,那必须确保它不会改变。全局变量在Session中设置之前必须首先在Drl文件中定义。

 

global java.util.List list        

 

随着Rule Base现在知道全局变量的定义和它的类型,任何Session现在可以调用session.setGlobal;如果之前对全局变量的类型和定义有错误,将会抛出异常。使用session.setGlobal(identifier, value)将全局变量设置到Session;

 

List list = new ArrayList();

session.setGlobal("list", list);      

     

如果一条规则在你 setGlobal 之前调用了定义的 Global ,会抛出一个 NullPointerException

2.5.4.6. 影子(shadow)Facts

影子Fact是一个对象的浅拷贝,用来缓存被设置到working memory 中的对象的拷贝。术语shadow facts来自JESS(Java Expert System Shell)的一个特性。

影子Fact的用意是回溯真值维护的概念。基本思路是,专家系统应当确保推导出的结论是准确的。一个运行中的应用系统可能在规则引擎的推导过程中改变了fact的数据。当发生这种情况时,规则引擎必须知道这种改变发生了,并进行适当的处理。通常有两种方法来保证真实性。第一种是在推论期间锁定所有的Facts。第二种是建立一个对象的缓存,并且强迫所有的改变必须通过规则引擎进行。在这种方法下,改变能够有序的进行。Shadow facts在多线程的环境中是非常重要的,当一个规则引擎被多个Session共享时。缺少了真值维护,系统无法证明结论是正确的。Shadow facts带来的主要优点是它使得开发更加容易。当需要开发者强制保持对fact修改的跟踪时,是非常容易出错,并且很难调试的。在没有增加对fact的更改进行跟踪的功能时,使用规则引擎建立一个有一定复杂度的系统是非常困难的。

Drools4.0对影子Fact有完全的支持,使用完全透明的lazy代理实现这一机制。影子fact机制默认是启用的,不会从外部的代码中看出来,甚至是在规则代码块中的代码。

虽然影子fact是确保引擎完整性的一个很好的方法,但是它们也给推论过程增加了一些负载。因此Drools4.0支持对它们更细致的控制,能够对单独的每一个类启用/禁用它们。要在所有的类上禁用影子fact,在系统特性的配置中设置以下特性:

drools.shadowProxy = false

 

另外,它也可以通过一个API调用来禁用它:

RuleBaseConfiguration conf = new RuleBaseConfiguration();

conf.setShadowProxy( false );

...

RuleBase ruleBase = RuleBaseFactory.newRuleBase( conf );

 

只对列表中的类禁用影子代理,使用下面的特性:

drools.shadowproxy.exclude = org.domainy.* org.domainx.ClassZ

 

如上所示,使用空格来分隔多于一个类的列表,并且使用'*'来作为广义字符。

 

注意: 对一个类禁用影子fact,意味着解除了引擎对类属性发生变化时的跟踪机制。它意味着一旦fact杯插入引擎后,不能有任何改变,否则会带来不可预期的行为。即使使用update()都没有帮助。在影子机制被禁止时,唯一安全的修改fact属性的方法是在属性改变前调用modifyRetract(),改变后调用modifyAssert()

2.5.4.7. 属性更改监听器(Property Change Listener

如果你的 fact 对象是 JavaBean ,你可以为它们实现一个 property change listener ,然后把它告诉规则引擎。这意味着,当一个 fact 改变时,规则引擎将会自动知道,并进行响应的动作(你不需要调用 modifyObject() 方法来通知 WorkingMemory )。代理库将会帮助实现这一切。要让 Property Change Listener 生效,还要将 fact 设置为动态( dynamic )模式,通过将 true 做为 assertObject() 方法的第二个参数来实现: 

Cheese stilton = new Cheese("stilton");

//specifies that this is a dynamic fact            

FactHandle stiltonHandle = workingMemory.insert( stilton, true );  

然后要在 JavaBean 中加入一个 PropertyChangeSupport 实例,和两个方法: addPropertyChangeListener()  removePropertyChangeListener() 。最后要在 JavaBean  setter 方法中通知 PropertyChangeSupport 所发生的变化。示例代码如下: 

 

private final PropertyChangeSupport changes = new PropertyChangeSupport( this );

...

public void addPropertyChangeListener(final PropertyChangeListener l) {

    this.changes.addPropertyChangeListener( l );

}

 

public void removePropertyChangeListener(final PropertyChangeListener l) {

    this.changes.removePropertyChangeListener( l );

}

...

 

public void setState(final String newState) {

    String oldState = this.state;

    this.state = newState;

    this.changes.firePropertyChange( "state",

                                      oldState,

                                      newState );

}              

2.5.4.8. Initial Fact

为了支持像not这样的条件元素,需要让引擎获得一个Initial Fact的种子。这个fact有着特定作用,不会被用户访问到。

在一个新的Working Memory最初的动作(insert, fireAllRules)中,这个Initial Fact将通过Rete网络传播。这允许规则没有LHS,或者不使用常规的fact(例如规则利用from从外部将数据拉过来)。例如,假设一个新的Working Memory已经建立,并且还没有任何Fact被设置,调用fireAllRules将导致Initial Fact的传播,并可能激活规则(否则,在没有其它fact的情况下不会发生任何事情)。

2.5.5. StatefulSession

Figure 2.20. StatefulSession

StatefulSession继承了WorkingMemory类,它只是简单的加上了异步方法和一个dispose()方法。

Example 2.7. 创建StatefulSession

StatefulSession session = ruleBase.newStatefulSession();

 

2.5.6. StatelessSession

Figure 2.21. StatelessSession

StatelessSession包容了WorkingMemory,而不是继承它,它的主要焦点是在决策服务类型的场景(decision service type scenarios)上。

Example 2.8. 创建StatelessSession

StatelessSession session = ruleBase.newStatelessSession();

 

Api因为问题域的原因而减少,并且变得更简单;这也意味着维护这些服务更加简单。RuleBase从不维护一个指向StatelessSession的引用,因此dispose()是不需要的,它只有一个execute()方法来获取一个对象或一组对象,没有insertfireAllRules方法。Execute方法遍历所有插入的对象,并在最后调用fireAllRules()方法;Session完成。可以使用executeWithResults方法返回需要查询的Session的结果信息,方法返回一个StatelessSessionResult对象。原因是在远程环境下,你并不总是需要一个返回的负载(返回消息所消耗的网络时间等),因此返回是可选的。

 

setAgendaFilter, setGlobal setGlobalResolver在整个Session期间共享它们的状态,因此每个execute()调用将使用设置好的AgendaFilter,或者查看是否有任何之前设置的全局变量等等。

 

StatelessSession当前不支持propertyChangeLisstenersExcute方法的异步执行版本被支持,记得在特定被管理的线程环境中(如JEE)覆盖ExecutorService实现。

 

ExecutorServic也支持顺序模型,这是一个特别的优化模式,将使用更少的内存和更快的执行速度;请在Sequential章节查看详细说明。

2.5.7. Agenda

Figure 2.22. 两阶段执行

Agenda  RETE 的一个特点。在一个 WorkingMemory 操作发生时,可能会有多条规则发生完全匹配。当一条规则完全匹配的时候,一个 Activation 就被创建(引用了这条规则和与其匹配的 facts ),然后放进 Agenda 中。 Agenda 通过使用冲突解决策略( Conflict Resolution Strategy )来安排这些 Activations 的执行。 

引擎工作在一个“ 2 阶段”模式下: 

1  WorkingMemory Actions  assert 新的 facts ,修改存在的 facts  retract facts 都是 WorkingMemory Actions 。通过在应用程序中调用 fireAllRules() 方法,会使引擎转换到 Agenda Evaluatioin 阶段。 

2  Agenda Evaluation :尝试选择一条规则进行激发( fire )。如果规则没有找到就退出,否则它就尝试激发这条规则,然后转换到 WorkingMemory Actions 阶段,直到 Agenda 中为空。 

Figure 2.23. 两阶段执行

这个过程一直重复,直到 Agenda 是空的,此时控制权就回到应用程序中。。当 WorkingMemory Actions 被发生时,没有规则被激发。 

2.5.7.1. Conflict Resultion

当有多条 rules  agenda 中,就需要解决冲突。当激发一条规则时,会对 WorkingMemory 产生副作用。规则引擎需要知道规则要以什么顺序来激发(例如,激发 rule A 可能会引起 rule B 被从 agenda 中移除。) 

Drools 采取的冲突解决策略有 2 种,按照优先级排列如下: Salience  LIFO (后进先出)。最易懂的策略是“ Salience ”,即优先级, 用户可以为某个 rule 指定一个高一点的优先级(通过附给它一个比较大的数字)。高 Salience  rule 将会被优先激发。LIFO 的优先级基于分配给Working Memory 操作(Action)的计数值, 从同样的Action建立的多个规则具有相同的值,相同值的规则执行顺序被看作可任意进行。

作为一个约定,不要将规则考虑为按照特定的顺序执行是一个好习惯,这样在编辑规则时不用考虑流的顺序。

可以通过调用RuleBaseConfiguration.setConflictResolver()方法来设置自定义的冲突解决机制,或者使用特性"drools.conflictResolver"

2.5.7.2. Agenda Groups

Agenda Groups 是划分 Agenda  rules (其实是“ activations ”)的一种方法。在任意一个时刻,只有一个 group 拥有“ focus ”,这意味着只有在那个 group 中的 activations 才是有效的。 

它们有时在CLIPS术语中被称为模块"modules" Agenda Groups 是在 grouped rules 之间创建一个“流”( flow )的简便的方法。你可以在规则引擎中,或是用 API 来切换具有焦点的组。如果你的规则有很明确的多“阶段”( phases )或多“序列”( sequences )的处理,可以考虑用 Agenda Groups 来达到这个目的。 

每次调用 setFocus() 方法的时候,那个 Agenda Group 就会被压入一个堆栈,当这个有焦点的组为空时,它就会被弹出,然后下一个组就会被执行。一个 Agenda Group 可以出现在堆栈的多个位置。默认的 Agenda Group “ MAIN ”,所有没有被指定 Agenda Group  Activations 都被放到那个组中,这个组总是被放在堆栈的第一个组,并默认给予焦点。 

 

2.5.7.3. Agenda Filters

Figure 2.24. AgendaFilters

 

过滤器是过滤器接口的可选实现,被用来允许/阻止一个激活的规则被激发(哪一个过滤器被使用完全依赖于执行)。Drools提供下面的已经默认实现的过滤器。

· RuleNameEndWithAgendaFilter

· RuleNameEqualsAgendaFilter

· RuleNameStartsWithAgendaFilter

· RuleNameMatchesAgendaFilter

要使用一个 filter 就要在调用 fireAllRules() 方法的时候指定它。下面的例子将对所有名字以“ Test ”结尾的规则进行过滤: 

workingMemory.fireAllRules( new RuleNameEndsWithAgendaFilter( "Test" ) );      

2.5.8. Truth Maintenance with Logical Objects

通常在将一个Fact插入到Working Memory后,需要显式的删除该对象。但使用逻辑插入(logical assertions),当最初插入该对象的条件已经不再为真时,Fact

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值