前言
写这篇文章的时候,我还在做一个叫规则编辑器的组件,当时要用到规则引擎,并且制定一个协议,于是寻找一些业内的方案发现了json-logic,鉴于此次的调研我发现了一个平常写代码的优化点。
什么是规则引擎
规则引擎由推理引擎发展而来,是一种嵌入在应用程序中的组件,实现了将业务决策从应用程序代码中分离出来,并使用预定义的语义模块编写业务决策。接受数据输入,解释业务规则,并根据业务规则做出业务决策。 《百度百科》
以上是引用至百度百科对规则引擎解释的一段话,其实通俗点讲就是,规则引擎要做的事情就是:输入一段数据与定义好的规则进行匹配,然后做一些行为决策,这里的行为决策可以理解为执行一个函数。
太多if语句啦
很好知道了什么是规则引擎,那么这个规则引擎和我们这里的if语句又有什么关系呢,别急我们先来看一段代码:
// 贷款申请操作的处理
function check() {
// 是否输入正确用户名
if (this.checkUsername(this.username)) {
// 是否输入正确身份证号
if (this.checkIdCard(this.idCard)) {
// 请输入正确的电话号码
if (this.checkTel(this.tel)) {
// 担保人是本人
if (this.dbrIsSelf) {
// 提交操作
submit();
// 担保人不是本人
} else {
// 是否输入正确担保人用户名
if (this.checkUsername(this.dbrName)) {
// 是否输入正确担保人身份证号
if (this.checkIdCard(this.dbrIdCard)) {
// 是否输入正确担保人电话号码
if (this.checkTel(this.dbrTel)) {
// 提交操作
submit();
}
}
}
}
}
}
}
}
看到上面的if语句一层一层的嵌套是不是有点头疼?看着都眼花撩乱是不是?因为我们每次维护时要记住好几个逻辑判断分支,才能知道到底什么情况下才能得到那个结果,这种代码的可读性和可维护性自然就比较低了。
其实if语句嵌套的优化很多,感兴趣的同学可以自行搜索一下,不过目前我还未曾看到用规则引擎解决的方式。而本文的主角也是规则引擎,那么我将用规则引擎的方式来解决这个if语句嵌套的问题。
自制一个简易版的规则引擎
上面我们了解到了什么是规则引擎以及接下来它要解决的问题,那么我们现在就开始着手制作一个简易版的规则引擎,我们知道规则引擎要运作起来就需要一个规则协议去解析一个规则然后再去匹配相应的输入数据。
所以这里我们使用 json-logic 作为我们规则引擎的协议以及规则的解析工具。
然后就是制作我们的“规则引擎”了,其实很简单,在json-logic的基础上加上一个规则匹配好之后可以有一个运行决策行为的函数就好了。关于json-logic的用法可以去GitHub了解一下:点击此处
首先,使用 npm 下载json-logic的包:npm i json-logic-js --save。
其次创建我们的规则引擎类:
import { merge } from 'lodash';
import jsonLogic, { RulesLogic } from 'json-logic-js';
interface Rules {
[ruleName: string]: any;
}
export class RuleEngine {
// 存储规则集合
private rules: Rules = {};
// 规则匹配后的状态
private state = 'pending';
// 规则匹配失败之后的错误信息
private errMsg = '';
constructor(rules: Rules) {
this.rules = rules;
}
add(rules: Rules) {
merge(this.rules, rules);
}
run(ruleName: string, data: any) {
if (this.rules[ruleName]) {
// 规则匹配成功
if (jsonLogic.apply(this.rules[ruleName], data)) {
this.state = 'resolved';
return this;
} else {
this.state = 'rejected';
this.errMsg = '规则不匹配';
return this;
}
} else {
this.state = 'rejected';
this.errMsg = '没找到此规则';
return this;
}
}
// 匹配成功后的执行函数
then(cb: any) {
this.state === 'resolved' && cb();
return this;
}
// 匹配失败后的执行函数
catch(errCb: any) {
this.state === 'rejected' && errCb(this.errMsg);
return this;
}
}
接下来我们分析一下开始的那个if嵌套的例子,实际上就是两种情况会通过会submit:担保人为本人的时候输入正确的个人信息可以通过和担保人为他人的时候输入正确的个人信息以及担保人的信息可以通过。
ok,分析完毕,开始操作!首先先创建一个数据输入对象:
// 担保人为本人
const obj1 = {
username: 'Eric',
idCard: '123456789',
tel: '0123456789',
dbrIsSelf: true,
dbrName: '',
dbrIdCard: '',
dbrTel: ''
};
// 担保人为他人
const obj2 = {
username: 'Eric',
idCard: '123456789',
tel: '0123456789',
dbrIsSelf: false,
dbrName: 'First',
dbrIdCard: '23456789',
dbrTel: '023456789'
};
根据json-logic规定的规则协议创建以上两种情况都可以通过的一条规则:
这里简单介绍一下下面这条规则的意思,具体的规则编写格式可以去上面写的 json-logic GitHub查看。
username == 'Eric' && idCard == '123456789' && tel == '0123456789' &&
(
dbrIsSelf == true
||
(dbrIsSelf == false && dbrName == 'First' && dbrIdCard == '23456789' && dbrTel == '023456789')
)
const rules = {
submit: {
and: [
{
'==': [{ var: 'username' }, 'Eric']
},
{
'==': [{ var: 'idCard' }, '123456789']
},
{
'==': [{ var: 'tel' }, '0123456789']
},
{
or: [
{
'==': [{ var: 'dbrIsSelf' }, true]
},
{
and: [
{
'==': [{ var: 'dbrIsSelf' }, false]
},
{
'==': [{ var: 'dbrName' }, 'First']
},
{
'==': [{ var: 'dbrIdCard' }, '23456789']
},
{
'==': [{ var: 'dbrTel' }, '023456789']
}
]
}
]
}
]
}
}
上面这条规则的“==”是规则协议里的一个操作符,同时并不仅仅是一些运算符,json-logic还支持自定义运算符通过函数运算相应的逻辑。具体实现细节可以 点击此处 查看。(ps:如果打不开就先开vpn)
好了,现在万事具备只欠东风啦~
创建一个RuleEngine的实例:
const ruleEngine = new RuleEngine(rules);
ruleEngine
.run('submit', obj1)
.then(() => {
console.log(`submit`);
})
.catch((errMsg: string) => console.log(errMsg));
// log -> submit
ruleEngine
.run('submit', obj2)
.then(() => {
console.log(`submit`);
})
.catch((errMsg: string) => console.log(errMsg));
// log -> submit
你会发现上面我们虽然只创建了一条规则,但是使用这一条规则,两种submit的情况都考虑进去了。后续如果还有submit的情况我们只需要修改规则或是增加规则。或许你会说:“本来我只是写几个if语句而已,现在搞的这么复杂还要写规则协议内的规则条件。“。没错现在来看确实效果不是特别明显,但是当你的if处理的逻辑过于复杂的时候,你就会发现你写的这些if嵌套越堆越多,以至于后续迭代维护的时候你自己都看不懂你写的代码了。如果只是一些简单的判断逻辑使用简单的if嵌套结合一些数组处理函数就够了,所以本文的目的是为了那些if语句嵌套了太深,判断逻辑过于复杂的情况,还望读者们酌情处理。