目录
[1. 基本整数算术运算](#1. 基本整数算术运算)
[2. 混合数值与浮点运算](#2. 混合数值与浮点运算)
[3. BigDecimal:规则计算中的首选数值类型](#3. BigDecimal:规则计算中的首选数值类型)
[4. 复合算术表达式与优先级](#4. 复合算术表达式与优先级)
[1. 数值比较](#1. 数值比较)
[2. 字符串比较的注意事项](#2. 字符串比较的注意事项)
[3. null 值比较:规则稳定性的关键](#3. null 值比较:规则稳定性的关键)
[1. 基本逻辑运算](#1. 基本逻辑运算)
[2. 复合逻辑表达式](#2. 复合逻辑表达式)
[3. 逻辑与比较的结合:规则的核心形态](#3. 逻辑与比较的结合:规则的核心形态)
[4. 短路求值:规则安全的最后防线](#4. 短路求值:规则安全的最后防线)
[1. 基本条件分支](#1. 基本条件分支)
[2. null 值处理的标准方式](#2. null 值处理的标准方式)
[3. 嵌套三元与可维护性问题](#3. 嵌套三元与可维护性问题)
[4. 与其他操作符组合使用](#4. 与其他操作符组合使用)
干货分享,感谢您的阅读!
在规则引擎体系中,变量和数据类型解决的是"规则可以感知哪些业务数据",而真正决定规则如何运转、如何做出判断的,往往不是复杂的语法结构,而是操作符本身。
在 QLExpress 中,操作符并不是零散的语法符号集合,而是一套高度贴近业务判断模型的规则表达工具。它们直接承载了金额计算、条件判断、区间约束、状态组合以及分支决策等核心能力,是规则系统从"能跑"走向"可维护、可扩展"的关键基础。
与许多规则 DSL 追求"表达能力最大化"不同,QLExpress 在操作符设计上选择了一条更务实的路径:
最大限度复用 Java 语义,最小化规则理解成本。
这意味着:
-
规则表达可以直接映射到工程师熟悉的计算与判断模型
-
业务规则不需要额外学习一套"新语言"
-
规则既能被机器稳定执行,也能被人快速审查和理解
本文将通过一套完整、可运行的示例代码,对 QLExpress 中最核心的四类操作符------算术、比较、逻辑与三元操作符进行系统性讲解,并从工程实践角度解释它们在真实规则系统中的设计意义与使用边界。

一、整体内容练习演示
我将提供 BasicOperatorsDemo,可以清晰看出,QLExpress 的操作符体系主要分为四大类:
-
算术操作符
-
比较操作符
-
逻辑操作符
-
三元操作符
这四类,基本覆盖了 90% 以上的规则表达需求。
展示详细代码如下:
java
package org.zyf.javabasic.qlexpress.basic.operators;
import com.ql.util.express.DefaultContext;
import com.ql.util.express.ExpressRunner;
import java.math.BigDecimal;
/**
* @program: zyfboot-javabasic
* @description: QLExpress基本操作符演示 - 全面展示算术、逻辑、比较等操作符
* @author: zhangyanfeng
* @create: 2025-12-25 23:51
**/
public class BasicOperatorsDemo {
private ExpressRunner runner;
public BasicOperatorsDemo() {
this.runner = new ExpressRunner();
System.out.println("✅ QLExpress基本操作符演示引擎初始化完成");
}
/**
* 演示算术操作符
*/
public void demonstrateArithmeticOperators() {
System.out.println("\n=== QLExpress算术操作符演示 ===\n");
try {
DefaultContext<String, Object> context = new DefaultContext<>();
// 准备测试数据
context.put("a", 10);
context.put("b", 3);
context.put("c", 2.5);
context.put("price", new BigDecimal("99.99"));
context.put("quantity", new BigDecimal("3"));
// 1. 基本算术操作
System.out.println("🔢 1. 基本算术操作演示:");
Object add = runner.execute("a + b", context, null, true, false);
Object subtract = runner.execute("a - b", context, null, true, false);
Object multiply = runner.execute("a * b", context, null, true, false);
Object divide = runner.execute("a / b", context, null, true, false);
Object modulo = runner.execute("a % b", context, null, true, false);
System.out.printf(" a + b = %s + %s = %s%n", context.get("a"), context.get("b"), add);
System.out.printf(" a - b = %s - %s = %s%n", context.get("a"), context.get("b"), subtract);
System.out.printf(" a * b = %s * %s = %s%n", context.get("a"), context.get("b"), multiply);
System.out.printf(" a / b = %s / %s = %s%n", context.get("a"), context.get("b"), divide);
System.out.printf(" a %% b = %s %% %s = %s%n%n", context.get("a"), context.get("b"), modulo);
// 2. 浮点数运算
System.out.println("💰 2. 浮点数运算演示:");
Object floatAdd = runner.execute("a + c", context, null, true, false);
Object floatMultiply = runner.execute("a * c", context, null, true, false);
Object floatDivide = runner.execute("a / c", context, null, true, false);
System.out.printf(" a + c = %s + %s = %s%n", context.get("a"), context.get("c"), floatAdd);
System.out.printf(" a * c = %s * %s = %s%n", context.get("a"), context.get("c"), floatMultiply);
System.out.printf(" a / c = %s / %s = %s%n%n", context.get("a"), context.get("c"), floatDivide);
// 3. BigDecimal精确运算
System.out.println("🎯 3. BigDecimal精确运算演示:");
Object bigAdd = runner.execute("price + 10.01", context, null, true, false);
Object bigMultiply = runner.execute("price * quantity", context, null, true, false);
Object bigDivide = runner.execute("price / 2", context, null, true, false);
System.out.printf(" price + 10.01 = %s + 10.01 = %s%n", context.get("price"), bigAdd);
System.out.printf(" price * quantity = %s * %s = %s%n", context.get("price"), context.get("quantity"), bigMultiply);
System.out.printf(" price / 2 = %s / 2 = %s%n%n", context.get("price"), bigDivide);
// 4. 复合运算
System.out.println("🧮 4. 复合运算演示:");
Object complex1 = runner.execute("(a + b) * c", context, null, true, false);
Object complex2 = runner.execute("a * b + c", context, null, true, false);
Object complex3 = runner.execute("a / b + a % b", context, null, true, false);
Object complex4 = runner.execute("(a + b) / (a - b + 1)", context, null, true, false);
System.out.printf(" (a + b) * c = (%s + %s) * %s = %s%n", context.get("a"), context.get("b"), context.get("c"), complex1);
System.out.printf(" a * b + c = %s * %s + %s = %s%n", context.get("a"), context.get("b"), context.get("c"), complex2);
System.out.printf(" a / b + a %% b = %s / %s + %s %% %s = %s%n", context.get("a"), context.get("b"), context.get("a"), context.get("b"), complex3);
System.out.printf(" (a + b) / (a - b + 1) = (%s + %s) / (%s - %s + 1) = %s%n%n", context.get("a"), context.get("b"), context.get("a"), context.get("b"), complex4);
} catch (Exception e) {
System.err.println("❌ 算术操作符演示失败: " + e.getMessage());
e.printStackTrace();
}
}
/**
* 演示比较操作符
*/
public void demonstrateComparisonOperators() {
System.out.println("\n=== QLExpress比较操作符演示 ===\n");
try {
DefaultContext<String, Object> context = new DefaultContext<>();
// 准备测试数据
context.put("x", 10);
context.put("y", 5);
context.put("z", 10);
context.put("str1", "hello");
context.put("str2", "world");
context.put("str3", "hello");
// 1. 数值比较
System.out.println("🔍 1. 数值比较操作演示:");
Object eq = runner.execute("x == z", context, null, true, false);
Object ne = runner.execute("x != y", context, null, true, false);
Object gt = runner.execute("x > y", context, null, true, false);
Object ge = runner.execute("x >= z", context, null, true, false);
Object lt = runner.execute("y < x", context, null, true, false);
Object le = runner.execute("y <= x", context, null, true, false);
System.out.printf(" x == z: %s == %s = %s%n", context.get("x"), context.get("z"), eq);
System.out.printf(" x != y: %s != %s = %s%n", context.get("x"), context.get("y"), ne);
System.out.printf(" x > y: %s > %s = %s%n", context.get("x"), context.get("y"), gt);
System.out.printf(" x >= z: %s >= %s = %s%n", context.get("x"), context.get("z"), ge);
System.out.printf(" y < x: %s < %s = %s%n", context.get("y"), context.get("x"), lt);
System.out.printf(" y <= x: %s <= %s = %s%n%n", context.get("y"), context.get("x"), le);
// 2. 字符串比较
System.out.println("📝 2. 字符串比较操作演示:");
Object strEq = runner.execute("str1 == str3", context, null, true, false);
Object strNe = runner.execute("str1 != str2", context, null, true, false);
Object strEqMethod = runner.execute("str1.equals(str3)", context, null, true, false);
System.out.printf(" str1 == str3: '%s' == '%s' = %s%n", context.get("str1"), context.get("str3"), strEq);
System.out.printf(" str1 != str2: '%s' != '%s' = %s%n", context.get("str1"), context.get("str2"), strNe);
System.out.printf(" str1.equals(str3): '%s'.equals('%s') = %s%n%n", context.get("str1"), context.get("str3"), strEqMethod);
// 3. null值比较
System.out.println("❓ 3. null值比较操作演示:");
context.put("nullValue", null);
context.put("nonNullValue", "not null");
Object nullEq = runner.execute("nullValue == null", context, null, true, false);
Object nullNe = runner.execute("nonNullValue != null", context, null, true, false);
Object nullCompare = runner.execute("nullValue == nonNullValue", context, null, true, false);
System.out.printf(" nullValue == null: %s == null = %s%n", context.get("nullValue"), nullEq);
System.out.printf(" nonNullValue != null: '%s' != null = %s%n", context.get("nonNullValue"), nullNe);
System.out.printf(" nullValue == nonNullValue: %s == '%s' = %s%n%n", context.get("nullValue"), context.get("nonNullValue"), nullCompare);
// 4. 复合比较
System.out.println("🔗 4. 复合比较操作演示:");
Object range1 = runner.execute("x >= 5 && x <= 15", context, null, true, false);
Object range2 = runner.execute("y < 0 || y > 20", context, null, true, false);
Object complex = runner.execute("(x > y) && (str1 != str2)", context, null, true, false);
System.out.printf(" x >= 5 && x <= 15: %s >= 5 && %s <= 15 = %s%n", context.get("x"), context.get("x"), range1);
System.out.printf(" y < 0 || y > 20: %s < 0 || %s > 20 = %s%n", context.get("y"), context.get("y"), range2);
System.out.printf(" (x > y) && (str1 != str2): (%s > %s) && ('%s' != '%s') = %s%n%n",
context.get("x"), context.get("y"), context.get("str1"), context.get("str2"), complex);
} catch (Exception e) {
System.err.println("❌ 比较操作符演示失败: " + e.getMessage());
e.printStackTrace();
}
}
/**
* 演示逻辑操作符
*/
public void demonstrateLogicalOperators() {
System.out.println("\n=== QLExpress逻辑操作符演示 ===\n");
try {
DefaultContext<String, Object> context = new DefaultContext<>();
// 准备测试数据
context.put("isActive", true);
context.put("isExpired", false);
context.put("hasPermission", true);
context.put("isLocked", false);
context.put("age", 25);
context.put("score", 85);
// 1. 基本逻辑操作
System.out.println("🔐 1. 基本逻辑操作演示:");
Object and = runner.execute("isActive && hasPermission", context, null, true, false);
Object or = runner.execute("isExpired || isLocked", context, null, true, false);
Object not = runner.execute("!isExpired", context, null, true, false);
System.out.printf(" isActive && hasPermission: %s && %s = %s%n", context.get("isActive"), context.get("hasPermission"), and);
System.out.printf(" isExpired || isLocked: %s || %s = %s%n", context.get("isExpired"), context.get("isLocked"), or);
System.out.printf(" !isExpired: !%s = %s%n%n", context.get("isExpired"), not);
// 2. 复合逻辑操作
System.out.println("🧩 2. 复合逻辑操作演示:");
Object complex1 = runner.execute("isActive && !isExpired && hasPermission", context, null, true, false);
Object complex2 = runner.execute("(isActive || hasPermission) && !isLocked", context, null, true, false);
Object complex3 = runner.execute("!(isExpired || isLocked)", context, null, true, false);
System.out.printf(" isActive && !isExpired && hasPermission: %s && !%s && %s = %s%n",
context.get("isActive"), context.get("isExpired"), context.get("hasPermission"), complex1);
System.out.printf(" (isActive || hasPermission) && !isLocked: (%s || %s) && !%s = %s%n",
context.get("isActive"), context.get("hasPermission"), context.get("isLocked"), complex2);
System.out.printf(" !(isExpired || isLocked): !(%s || %s) = %s%n%n",
context.get("isExpired"), context.get("isLocked"), complex3);
// 3. 逻辑与数值比较结合
System.out.println("🎯 3. 逻辑与数值比较结合演示:");
Object ageCheck = runner.execute("age >= 18 && age <= 65", context, null, true, false);
Object scoreCheck = runner.execute("score >= 60 && isActive", context, null, true, false);
Object eligibility = runner.execute("(age >= 18) && (score >= 80) && isActive && !isExpired", context, null, true, false);
System.out.printf(" age >= 18 && age <= 65: %s >= 18 && %s <= 65 = %s%n", context.get("age"), context.get("age"), ageCheck);
System.out.printf(" score >= 60 && isActive: %s >= 60 && %s = %s%n", context.get("score"), context.get("isActive"), scoreCheck);
System.out.printf(" 综合资格检查: (%s >= 18) && (%s >= 80) && %s && !%s = %s%n%n",
context.get("age"), context.get("score"), context.get("isActive"), context.get("isExpired"), eligibility);
// 4. 短路求值演示
System.out.println("⚡ 4. 短路求值演示:");
context.put("nullObj", null);
Object shortCircuit1 = runner.execute("nullObj != null && nullObj.toString().length() > 0", context, null, true, false);
Object shortCircuit2 = runner.execute("nullObj == null || nullObj.toString().length() > 0", context, null, true, false);
System.out.printf(" 短路AND: nullObj != null && nullObj.toString().length() > 0 = %s%n", shortCircuit1);
System.out.printf(" 短路OR: nullObj == null || nullObj.toString().length() > 0 = %s%n%n", shortCircuit2);
} catch (Exception e) {
System.err.println("❌ 逻辑操作符演示失败: " + e.getMessage());
e.printStackTrace();
}
}
/**
* 演示三元操作符
*/
public void demonstrateTernaryOperator() {
System.out.println("\n=== QLExpress三元操作符演示 ===\n");
try {
DefaultContext<String, Object> context = new DefaultContext<>();
// 准备测试数据
context.put("score", 85);
context.put("age", 17);
context.put("balance", 1000);
context.put("name", null);
// 1. 基本三元操作
System.out.println("❓ 1. 基本三元操作演示:");
Object grade = runner.execute("score >= 60 ? '及格' : '不及格'", context, null, true, false);
Object ageGroup = runner.execute("age >= 18 ? '成年人' : '未成年人'", context, null, true, false);
Object balanceStatus = runner.execute("balance > 0 ? '有余额' : '无余额'", context, null, true, false);
System.out.printf(" score >= 60 ? '及格' : '不及格': %s >= 60 ? '及格' : '不及格' = %s%n", context.get("score"), grade);
System.out.printf(" age >= 18 ? '成年人' : '未成年人': %s >= 18 ? '成年人' : '未成年人' = %s%n", context.get("age"), ageGroup);
System.out.printf(" balance > 0 ? '有余额' : '无余额': %s > 0 ? '有余额' : '无余额' = %s%n%n", context.get("balance"), balanceStatus);
// 2. 处理null值
System.out.println("🔍 2. null值处理演示:");
Object nameCheck = runner.execute("name != null ? name : '匿名用户'", context, null, true, false);
Object nameLength = runner.execute("name != null ? name.length() : 0", context, null, true, false);
System.out.printf(" name != null ? name : '匿名用户': %s != null ? %s : '匿名用户' = %s%n", context.get("name"), context.get("name"), nameCheck);
System.out.printf(" name != null ? name.length() : 0: %s != null ? name.length() : 0 = %s%n%n", context.get("name"), nameLength);
// 3. 嵌套三元操作
System.out.println("🎯 3. 嵌套三元操作演示:");
// 使用分步方式处理复杂的嵌套三元操作,避免QLExpress解析器限制
String levelScript =
"if (score >= 90) return '优秀';" +
"else if (score >= 80) return '良好';" +
"else if (score >= 70) return '中等';" +
"else if (score >= 60) return '及格';" +
"else return '不及格';";
Object level = runner.execute(levelScript, context, null, true, false);
// 简化嵌套三元操作
Object discount = runner.execute("age < 12 ? 0.5 : (age < 18 ? 0.7 : (age > 65 ? 0.8 : 1.0))", context, null, true, false);
System.out.printf(" 成绩等级: %s = %s%n", context.get("score"), level);
System.out.printf(" 年龄折扣: %s岁 = %s折%n%n", context.get("age"), discount);
// 4. 与其他操作符结合
System.out.println("🔗 4. 与其他操作符结合演示:");
Object finalScore = runner.execute("(score >= 60 ? score : 0) + (age >= 18 ? 5 : 0)", context, null, true, false);
Object status = runner.execute("balance > 0 && age >= 18 ? '可用' : '不可用'", context, null, true, false);
System.out.printf(" 最终得分: (score >= 60 ? score : 0) + (age >= 18 ? 5 : 0) = %s%n", finalScore);
System.out.printf(" 账户状态: balance > 0 && age >= 18 ? '可用' : '不可用' = %s%n%n", status);
} catch (Exception e) {
System.err.println("❌ 三元操作符演示失败: " + e.getMessage());
e.printStackTrace();
}
}
/**
* 运行所有演示
*/
public void runAllDemonstrations() {
System.out.println("🚀 开始QLExpress基本操作符完整演示...\n");
demonstrateArithmeticOperators();
demonstrateComparisonOperators();
demonstrateLogicalOperators();
demonstrateTernaryOperator();
System.out.println("✅ QLExpress基本操作符演示完成!");
System.out.println("\n📚 学习要点:");
System.out.println(" 1. 算术操作符: +, -, *, /, % 支持各种数值类型");
System.out.println(" 2. 比较操作符: ==, !=, >, >=, <, <= 返回布尔值");
System.out.println(" 3. 逻辑操作符: &&, ||, ! 支持短路求值");
System.out.println(" 4. 三元操作符: condition ? value1 : value2 简化条件判断");
System.out.println(" 5. 操作符优先级: 算术 > 比较 > 逻辑,使用括号改变优先级");
System.out.println(" 6. BigDecimal运算保证精度,适合金融计算");
}
/**
* 主方法 - 运行演示
*/
public static void main(String[] args) {
BasicOperatorsDemo demo = new BasicOperatorsDemo();
demo.runAllDemonstrations();
}
}
基本验证打印结果如下:
python
✅ QLExpress基本操作符演示引擎初始化完成
🚀 开始QLExpress基本操作符完整演示...
=== QLExpress算术操作符演示 ===
🔢 1. 基本算术操作演示:
a + b = 10 + 3 = 13
a - b = 10 - 3 = 7
a * b = 10 * 3 = 30
a / b = 10 / 3 = 3
a % b = 10 % 3 = 1
💰 2. 浮点数运算演示:
a + c = 10 + 2.5 = 12.5
a * c = 10 * 2.5 = 25.0
a / c = 10 / 2.5 = 4.0
🎯 3. BigDecimal精确运算演示:
price + 10.01 = 99.99 + 10.01 = 110.00
price * quantity = 99.99 * 3 = 299.97
price / 2 = 99.99 / 2 = 50.00
🧮 4. 复合运算演示:
(a + b) * c = (10 + 3) * 2.5 = 32.5
a * b + c = 10 * 3 + 2.5 = 32.5
a / b + a % b = 10 / 3 + 10 % 3 = 4
(a + b) / (a - b + 1) = (10 + 3) / (10 - 3 + 1) = 1
=== QLExpress比较操作符演示 ===
🔍 1. 数值比较操作演示:
x == z: 10 == 10 = true
x != y: 10 != 5 = true
x > y: 10 > 5 = true
x >= z: 10 >= 10 = true
y < x: 5 < 10 = true
y <= x: 5 <= 10 = true
📝 2. 字符串比较操作演示:
str1 == str3: 'hello' == 'hello' = true
str1 != str2: 'hello' != 'world' = true
str1.equals(str3): 'hello'.equals('hello') = true
❓ 3. null值比较操作演示:
nullValue == null: null == null = true
nonNullValue != null: 'not null' != null = true
nullValue == nonNullValue: null == 'not null' = false
🔗 4. 复合比较操作演示:
x >= 5 && x <= 15: 10 >= 5 && 10 <= 15 = true
y < 0 || y > 20: 5 < 0 || 5 > 20 = false
(x > y) && (str1 != str2): (10 > 5) && ('hello' != 'world') = true
=== QLExpress逻辑操作符演示 ===
🔐 1. 基本逻辑操作演示:
isActive && hasPermission: true && true = true
isExpired || isLocked: false || false = false
!isExpired: !false = true
🧩 2. 复合逻辑操作演示:
isActive && !isExpired && hasPermission: true && !false && true = true
(isActive || hasPermission) && !isLocked: (true || true) && !false = true
!(isExpired || isLocked): !(false || false) = true
🎯 3. 逻辑与数值比较结合演示:
age >= 18 && age <= 65: 25 >= 18 && 25 <= 65 = true
score >= 60 && isActive: 85 >= 60 && true = true
综合资格检查: (25 >= 18) && (85 >= 80) && true && !false = true
⚡ 4. 短路求值演示:
短路AND: nullObj != null && nullObj.toString().length() > 0 = false
短路OR: nullObj == null || nullObj.toString().length() > 0 = true
=== QLExpress三元操作符演示 ===
❓ 1. 基本三元操作演示:
score >= 60 ? '及格' : '不及格': 85 >= 60 ? '及格' : '不及格' = 及格
age >= 18 ? '成年人' : '未成年人': 17 >= 18 ? '成年人' : '未成年人' = 未成年人
balance > 0 ? '有余额' : '无余额': 1000 > 0 ? '有余额' : '无余额' = 有余额
🔍 2. null值处理演示:
name != null ? name : '匿名用户': null != null ? null : '匿名用户' = 匿名用户
name != null ? name.length() : 0: null != null ? name.length() : 0 = 0
🎯 3. 嵌套三元操作演示:
成绩等级: 85 = 良好
年龄折扣: 17岁 = 0.7折
🔗 4. 与其他操作符结合演示:
最终得分: (score >= 60 ? score : 0) + (age >= 18 ? 5 : 0) = 85
账户状态: balance > 0 && age >= 18 ? '可用' : '不可用' = 不可用
✅ QLExpress基本操作符演示完成!
📚 学习要点:
1. 算术操作符: +, -, *, /, % 支持各种数值类型
2. 比较操作符: ==, !=, >, >=, <, <= 返回布尔值
3. 逻辑操作符: &&, ||, ! 支持短路求值
4. 三元操作符: condition ? value1 : value2 简化条件判断
5. 操作符优先级: 算术 > 比较 > 逻辑,使用括号改变优先级
6. BigDecimal运算保证精度,适合金融计算
Process finished with exit code 0
二、各部分详细讲解和指导说明
(一)算术操作符:规则计算能力的基础支撑
QLExpress 支持的算术操作符与 Java 保持一致:
cs
+ - * / %
从代码可以看到,算术操作主要围绕三种数据形态展开:
-
整数运算
-
浮点运算
-
BigDecimal 精确运算
1. 基本整数算术运算
Go
a + b
a - b
a * b
a / b
a % b
在 QLExpress 中:
-
整数运算行为 完全遵循 Java 的数值规则
-
/为整数除法时,结果仍为整数 -
%适合做区间、周期、取模类规则
这意味着:
QLExpress 不会为了"规则好写"而改变数学含义。
这种设计对于规则系统来说是优点,而不是限制:
-
行为可预测
-
不会引入"规则环境特有语义"
-
Java 工程师几乎零学习成本
2. 混合数值与浮点运算
vbnet
a + c
a * c
a / c
当整数与浮点数混合参与运算时,QLExpress 会进行自动类型提升:
-
int + double → double -
int / double → double
这一点与 Java 完全一致。
但需要强调的是:
在规则引擎中,浮点运算应当是"非主流选项"。
浮点运算适合用于:
-
权重
-
比例
-
非精确评分
不适合用于:
-
金额
-
结算
-
财务相关规则
3. BigDecimal:规则计算中的首选数值类型
css
price * quantity
price + 10.01
price / 2
现在提供的示例非常典型,也非常"工程正确"。
在 QLExpress 中:
-
BigDecimal 是原生支持的
-
运算结果仍为 BigDecimal
-
不会隐式降级为 double
这使得 QLExpress 非常适合用于:
-
金融规则
-
计费规则
-
风控阈值计算
一个重要的工程结论是:只要规则涉及金额,Context 中就不应该出现 double。
4. 复合算术表达式与优先级
cpp
(a + b) * c
a * b + c
a / b + a % b
QLExpress 的算术优先级规则与 Java 一致:
-
括号
-
乘 / 除 / 取模
-
加 / 减
因此:
-
推荐在复杂表达式中主动加括号
-
不要依赖"读者的优先级记忆"
在规则系统中,可读性比简洁性更重要。
(二)比较操作符:规则判断的逻辑入口
比较操作符的核心目标只有一个:
将"值"转换为"是 / 否"的判断结果。
QLExpress 支持的比较操作符包括:
Dart
== != > >= < <=
1. 数值比较
Delphi
x == z
x != y
x > y
x >= z
在数值比较上,QLExpress 的行为是:
-
基于数值大小,而不是对象引用
-
返回标准 boolean
这使得数值区间判断非常自然:
java
x >= 5 && x <= 15
这是规则引擎中最常见的模式之一。
2. 字符串比较的注意事项
java
str1 == str3
str1 != str2
str1.equals(str3)
这里需要特别说明:
-
==在 QLExpress 中用于 值相等判断 -
与 Java 中的"引用比较"语义不同
但在工程实践中,更推荐:
java
str1.equals(str3)
原因并不是"QLExpress 的 == 不可靠",而是:
规则表达应尽量贴近 Java 语义,减少理解歧义。
3. null 值比较:规则稳定性的关键
java
nullValue == null
nonNullValue != null
QLExpress 对 null 的处理非常"克制":
-
不自动兜底
-
不隐式转换
-
不吞异常
这意味着:
所有 null 安全,都必须由规则显式表达。
这是规则系统可维护性的基础。
(三)逻辑操作符:规则组合的骨架
逻辑操作符决定了多个判断条件如何组合成一条业务规则。
QLExpress 支持:
javascript
&& || !
并且支持短路求值。
1. 基本逻辑运算
Kotlin
isActive && hasPermission
isExpired || isLocked
!isExpired
这些写法与 Java 完全一致,没有任何"规则特化语义"。
这使得业务规则具备非常高的可读性。
2. 复合逻辑表达式
java
isActive && !isExpired && hasPermission
(isActive || hasPermission) && !isLocked
在规则系统中,这类表达式往往对应:
-
用户是否可用
-
订单是否可操作
-
资源是否可访问
工程建议:
当逻辑条件超过 3 个时,应考虑拆分或加括号,避免"逻辑黑洞"。
3. 逻辑与比较的结合:规则的核心形态
objectivec
(age >= 18) && (score >= 80) && isActive && !isExpired
这是最典型的规则表达形式:
-
比较操作负责"判断"
-
逻辑操作负责"组合"
QLExpress 在这方面的设计非常纯粹,没有引入额外 DSL 概念。
4. 短路求值:规则安全的最后防线
perl
nullObj != null && nullObj.toString().length() > 0
短路求值在规则系统中极其重要,它直接决定了:
-
是否会触发 NPE
-
规则是否稳定
QLExpress 的 &&、|| 严格支持短路行为,这一点在真实生产环境中非常关键。
(四)三元操作符:规则表达的"条件分支工具"
三元操作符是 QLExpress 中最具表达力、也最容易被滥用的操作符:
php
condition ? value1 : value2
1. 基本条件分支
java
score >= 60 ? '及格' : '不及格'
这种写法非常适合:
-
状态映射
-
简单分类
-
默认值兜底
2. null 值处理的标准方式
java
name != null ? name : '匿名用户'
这是 QLExpress 中处理 null 的推荐范式:
-
显式
-
清晰
-
不依赖隐式规则
3. 嵌套三元与可维护性问题
我在代码中特意给出了两种方案:
-
使用 if / else 语句
-
使用嵌套三元表达式
这实际上反映了一个重要原则:
当规则复杂到影响可读性时,不要强行用三元。
QLExpress 支持脚本式写法,本身就是为了提升复杂规则的可维护性。
4. 与其他操作符组合使用
java
balance > 0 && age >= 18 ? '可用' : '不可用'
这种写法在规则系统中非常常见,但需要注意优先级问题。
工程建议:
三元操作符前的条件部分,最好始终加括号。
三、小结:操作符不是语法细节,而是规则建模工具
通过对 QLExpress 基本操作符的系统演示与逐项拆解,可以得出几个非常明确、且具有工程指导意义的结论。
首先,操作符不是语法层面的细节问题,而是规则建模能力的直接体现。
每一个 +、&&、>= 或 ?:,本质上都在描述业务世界中的某种计算关系或决策路径。
其次,QLExpress 在操作符设计上刻意保持与 Java 语义的一致性,这并不是保守,而是一种极具工程价值的选择。它显著降低了规则编写、评审与排错的心智成本,使规则代码能够长期保持可读性。
第三,BigDecimal 在规则计算中应当成为默认选择。示例中已经清晰表明,只要涉及金额、结算或精度敏感计算,将 double 暴露在规则上下文中,本身就是一种风险设计。
第四,逻辑操作符与短路求值是规则系统稳定性的"安全阀"。
正确利用 &&、|| 的短路特性,可以在不增加额外判断代码的情况下,有效避免空指针和非法状态传播。
最后,三元操作符是一把"双刃剑"。它可以极大提升规则表达的紧凑度,但一旦嵌套失控,就会迅速侵蚀规则的可维护性。QLExpress 提供脚本式写法,正是为复杂分支逻辑保留了一条工程上的"退路"。
**归根结底,优秀的规则不是写出来的,而是被"建模"出来的。**而操作符,正是规则建模过程中最基础、也最关键的构件。
参考链接
-
QLExpress 官方 GitHub
https://github.com/alibaba/QLExpress -
QLExpress 官方使用文档
-
Java BigDecimal 官方文档
https://docs.oracle.com/javase/8/docs/api/java/math/BigDecimal.html -
规则引擎设计实践(Martin Fowler)
-
表达式语言与规则系统对比综述
https://martinfowler.com/bliki/DomainSpecificLanguage.html