深入探索Jess:从Web应用到Java嵌入的全面指南
1. Jess查询与订单管理
Jess查询可包含变量声明,其作用是列出查询的参数。在运行查询函数时,所提供的参数会与模式中同名的变量绑定,这样就能在运行时指定查询模式要匹配的具体值。
例如,列出订单中所有商品的查询:
(defquery items-for-order
(declare (variables ?order))
(line-item (order-number ?order) (part-number ?part))
(product (part-number ?part)))
此查询返回的每个匹配项都会包含一个商品编号事实和相关的产品事实,这些信息可用于计算订单总价、打印发货清单等。
推荐订单查询与之类似:
(defquery recommendations-for-order
(declare (variables ?order))
(recommend (order-number ?order) (part-number ?part))
(product (part-number ?part)))
这里的 ?part 是内部变量,仅用于在查询模式中进行匹配。
在Web应用里,每个订单都需有唯一编号。传统做法是在数据库中使用存储过程,而在Jess中,可使用 deftemplate 和 deffunction 实现相同功能。
deftemplate 定义如下:
(deftemplate next-order-number
(slot value))
查询获取该模板的事实:
(defquery order-number
(next-order-number))
deffunction 返回下一个订单编号:
(deffunction get-new-order-number ()
(bind ?it (run-query order-number))
(if (not (?it hasNext)) then
(assert (next-order-number (value 1002)))
(return 1001)
else
(bind ?token (?it next))
(bind ?fact (?token fact 1))
(bind ?number (?fact getSlotValue value))
(modify ?fact (value (+ ?number 1)))
(return ?number)))
若不存在 next-order-number 事实,该函数会创建一个并返回最低可能的订单编号(这里指定为1001);若存在,则递增该值、修改事实并返回未递增的订单编号。
2. 清理部分填写的订单
Web用户行为难以预测,可能会取消交易或在操作过程中返回。因此,Web应用需要一种方法来重新初始化部分填写的订单。可通过移除工作内存中的所有 line-items 、 recommends 和订单事实来实现,因为应用能根据其他状态信息重新断言这些事实。
可使用 defmodule 和自动聚焦规则进行清理:
(defmodule CLEANUP)
(defrule CLEANUP::initialize-order-1
(declare (auto-focus TRUE))
(MAIN::initialize-order ?number)
?item <- (line-item (order-number ?number))
=>
(retract ?item))
(defrule CLEANUP::initialize-order-2
(declare (auto-focus TRUE))
(MAIN::initialize-order ?number)
?rec <- (recommend (order-number ?number))
=>
(retract ?rec))
(defrule CLEANUP::initialize-order-3
(declare (auto-focus TRUE))
?init <- (MAIN::initialize-order ?number)
(not (line-item (order-number ?number)))
(not (recommend (order-number ?number)))
=>
(retract ?init))
(defrule CLEANUP::clean-up-order
(declare (auto-focus TRUE))
?clean <- (MAIN::clean-up-order ?number)
?order <- (order (order-number ?number))
=>
(assert (initialize-order ?number))
(retract ?clean ?order))
这些规则的作用如下:
- initialize-order-1 :当看到 initialize-order 事实时,删除所有 line-item 事实。
- initialize-order-2 :删除所有 recommend 事实。
- initialize-order-3 :当没有更多 line-items 或推荐时,删除 initialize-order 事实。
- clean-up-order :作为清理入口,撤回订单事实,并通过断言 initialize-order 事实触发其他规则。
需注意,这些规则应是独立模块中的自动聚焦规则,否则可能引发“断言风暴”,可通过移除所有必要事实或调用 Rete.halt() 方法停止。
3. 嵌入Jess到Java应用
以往的应用多基于命令行工具 jess.Main ,但实际上它只是Jess库的命令行包装。使用Jess库,可在Java代码中创建任意数量的Jess推理引擎,定义规则、添加数据、在不同线程中运行并收集结果。
Jess库核心是 jess.Rete 类,其实例可看作Jess实例,有独立的工作内存、规则列表和函数集。创建 jess.Rete 对象很简单,有默认构造函数:
import jess.*;
...
Rete engine = new Rete();
常用构造函数接受 java.lang.Object 参数,用于指定类加载器:
Rete engine = new Rete(someObject);
3.1 executeCommand 方法
在Java程序创建 Rete 对象后,可使用 executeCommand 方法与Jess交互。该方法接受字符串参数,解释为Jess语言表达式并返回 jess.Value 。
示例:添加事实到Jess工作内存并获取 jess.Fact 对象:
import jess.*;
...
Rete engine = new Rete();
Value v = engine.executeCommand("(assert (color red))");
Fact f = v.factValue(engine.getGlobalContext());
executeCommand 具有线程安全的优点,同一时间只有一个调用能在给定的 jess.Rete 实例上执行。
3.2 交换Java对象
在Jess和Java之间交换对象可使用 store 和 fetch 函数。
从Java发送对象到Jess的步骤:
1. 选择唯一标识符。
2. 在Java中调用 Rete.store ,传入标识符和对象。
3. 在Jess中调用 (fetch) ,使用相同标识符检索对象。
示例:
import jess.*;
import java.awt.Color;
...
Rete engine = new Rete();
Color pink = new Color(255, 200, 200);
engine.store("PINK", pink);
Value v = engine.executeCommand("(assert (color (fetch PINK)))");
Fact f = v.factValue(engine.getGlobalContext());
从Jess获取结果到Java也可使用相同方法:
import jess.*;
...
Rete engine = new Rete();
engine.executeCommand("(assert (numbers 2 2))");
String rule = "(defrule add-numbers" +
"(numbers ?n1 ?n2)" +
"=>" +
"(store SUM (+ ?n1 ?n2)))";
engine.executeCommand(rule);
engine.executeCommand("(run)");
Value sumValue = engine.fetch("SUM");
int sum = sumValue.intValue(engine.getGlobalContext());
3.3 executeCommand 的局限性与替代方法
executeCommand 虽方便,但存在一些问题,如冗长、可能效率低、易出错。建议尽可能使用Jess的直接Java API。
以下是 jess.Rete 类的一些方法及其对应的Jess函数:
| Rete方法 | Jess等效 |
| ---- | ---- |
| reset() | (reset) |
| run() | (run) |
| run(int) | (run number) |
| clear() | (clear) |
例如,可将 engine.executeCommand("(run)"); 替换为 engine.run(); 。
4. 在Java中处理Fact对象
可直接构造 jess.Fact 对象,而非使用 executeCommand 、 store 和 fetch 。这种方法在单个事实包含多个槽位对象时更方便高效。
以下是 jess.Rete 类处理事实的一些方法:
| Rete方法 | Jess等效 |
| ---- | ---- |
| addDeftemplate(Deftemplate) | (deftemplate) |
| assertFact(Fact) | (assert fact) |
| findDeftemplate(String) | 无 |
| findFactByFact(Fact) | 无 |
| findFactById(int) | (fact-id number) |
| retract(Fact) | (retract fact-id) |
示例代码展示如何创建 Deftemplate 并断言 Fact 对象:
import jess.*;
public class CreateFacts {
public static void main(String[] unused)
throws JessException {
Rete engine = new Rete();
Deftemplate d =
new Deftemplate("person", "A person", engine);
d.addSlot("name", Funcall.NIL, "STRING");
d.addSlot("address", Funcall.NIL, "STRING");
engine.addDeftemplate(d);
String[][] data = {
{"Joe Smith", "123 Main Street"},
{"Fred Jones", "333 Elm Circle"},
{"Bob Weasley", "211 Planet Way"},
};
for (int i=0; i<data.length; ++i) {
Fact f = new Fact("person", engine);
f.setSlotValue("name",
new Value(data[i][0], RU.STRING));
f.setSlotValue("address",
new Value(data[i][1], RU.STRING));
engine.assertFact(f);
}
engine.executeCommand("(facts)");
}
}
需注意,断言 Fact 对象后,不能更改其槽位值,撤回事实后对象才会释放。
通过以上内容,我们全面了解了Jess在Web应用和Java嵌入中的使用方法,包括查询构建、订单管理、清理规则以及在Java中与Jess交互和处理事实的技巧。这些知识为开发基于Jess的复杂应用提供了坚实基础。
深入探索Jess:从Web应用到Java嵌入的全面指南
5. 总结与展望
Jess在Web应用和Java嵌入方面展现出了强大的功能和灵活性。通过前面的介绍,我们掌握了Jess查询的构建、订单管理、清理规则以及在Java中与Jess交互和处理事实的技巧。
Jess查询允许我们通过变量声明指定查询参数,在运行时灵活匹配具体值。例如, items-for-order 和 recommendations-for-order 查询能帮助我们获取订单相关的商品和推荐信息。在订单管理方面,我们学习了如何为每个订单分配唯一编号,以及如何清理部分填写的订单。
在Java中嵌入Jess时, jess.Rete 类是核心,它提供了丰富的方法来与Jess交互。 executeCommand 方法方便我们执行Jess表达式,但也存在一些局限性,因此我们可以使用更直接的Java API来提高效率和减少错误。同时, store 和 fetch 函数让我们能够在Jess和Java之间交换对象。
在处理 Fact 对象时,我们可以直接构造 jess.Fact 对象,这在处理包含多个槽位的事实时更加高效。
以下是Jess在Web应用和Java嵌入中的关键技术点总结:
1. Jess查询 :通过变量声明指定查询参数,实现灵活匹配。
2. 订单管理 :使用 deftemplate 和 deffunction 为订单分配唯一编号,使用自动聚焦规则清理部分填写的订单。
3. Java嵌入 :使用 jess.Rete 类创建Jess实例,通过 executeCommand 方法与Jess交互,使用 store 和 fetch 函数交换对象。
4. Fact对象处理 :直接构造 jess.Fact 对象,使用 Rete 类的相关方法处理事实。
未来,我们可以基于这些知识开发更复杂的基于Jess的应用。例如,我们可以添加更多的规则来支持销售价格和买一送一等促销活动。同时,我们可以将Jess与其他技术结合,如数据库、机器学习算法等,以实现更强大的功能。
6. 实际应用示例:构建简单的推荐系统
为了更好地理解如何将Jess应用到实际项目中,我们将构建一个简单的推荐系统。这个系统将根据用户的订单信息提供相关的商品推荐。
6.1 Jess代码
首先,我们需要定义一些Jess规则和模板。以下是相关的Jess代码:
; 定义商品模板
(deftemplate product
(slot part-number)
(slot name)
(slot price))
; 定义订单行项目模板
(deftemplate line-item
(slot order-number)
(slot part-number))
; 定义推荐模板
(deftemplate recommend
(slot order-number)
(slot part-number))
; 定义查询,获取订单中的商品
(defquery items-for-order
(declare (variables ?order))
(line-item (order-number ?order) (part-number ?part))
(product (part-number ?part)))
; 定义查询,获取订单的推荐商品
(defquery recommendations-for-order
(declare (variables ?order))
(recommend (order-number ?order) (part-number ?part))
(product (part-number ?part)))
; 定义规则,根据订单中的商品生成推荐
(defrule generate-recommendations
(line-item (order-number ?order) (part-number ?part1))
(product (part-number ?part1) (name ?name1))
(product (part-number ?part2) (name ?name2))
(test (not (eq ?part1 ?part2)))
=>
(assert (recommend (order-number ?order) (part-number ?part2))))
6.2 Java代码
接下来,我们将使用Java代码来加载Jess规则并执行查询。以下是Java代码示例:
import jess.*;
public class RecommendationSystem {
public static void main(String[] args) throws JessException {
// 创建Rete引擎
Rete engine = new Rete();
// 加载Jess规则
engine.executeCommand("(batch \"recommendations.clp\")");
// 插入一些商品事实
engine.executeCommand("(assert (product (part-number 1) (name \"Product 1\") (price 10)))");
engine.executeCommand("(assert (product (part-number 2) (name \"Product 2\") (price 20)))");
engine.executeCommand("(assert (product (part-number 3) (name \"Product 3\") (price 30)))");
// 插入订单行项目事实
engine.executeCommand("(assert (line-item (order-number 1) (part-number 1)))");
// 运行规则
engine.executeCommand("(run)");
// 执行查询,获取订单中的商品
Value v = engine.executeCommand("(run-query items-for-order 1)");
FactIterator iterator = v.factIteratorValue(engine.getGlobalContext());
while (iterator.hasNext()) {
Fact fact = iterator.next();
System.out.println("Order item: " + fact.getSlotValue("part-number").stringValue(engine.getGlobalContext()));
}
// 执行查询,获取订单的推荐商品
v = engine.executeCommand("(run-query recommendations-for-order 1)");
iterator = v.factIteratorValue(engine.getGlobalContext());
while (iterator.hasNext()) {
Fact fact = iterator.next();
System.out.println("Recommendation: " + fact.getSlotValue("part-number").stringValue(engine.getGlobalContext()));
}
}
}
6.3 操作步骤
以下是构建这个简单推荐系统的操作步骤:
1. 编写Jess规则 :将上述Jess代码保存为 recommendations.clp 文件。
2. 编写Java代码 :将上述Java代码保存为 RecommendationSystem.java 文件。
3. 编译Java代码 :使用 javac 命令编译Java代码。
4. 运行Java程序 :使用 java 命令运行编译后的Java程序。
通过以上步骤,我们可以看到订单中的商品和相关的推荐商品。
7. 流程图:推荐系统工作流程
graph LR
A[开始] --> B[加载Jess规则]
B --> C[插入商品和订单事实]
C --> D[运行规则]
D --> E[执行查询获取订单商品]
E --> F[输出订单商品]
D --> G[执行查询获取推荐商品]
G --> H[输出推荐商品]
H --> I[结束]
这个流程图展示了推荐系统的工作流程,从加载规则到输出推荐商品的整个过程。
通过以上内容,我们深入了解了Jess在Web应用和Java嵌入中的使用方法,并通过实际示例展示了如何构建一个简单的推荐系统。希望这些知识能帮助你在实际项目中更好地应用Jess。
超级会员免费看

3574

被折叠的 条评论
为什么被折叠?



