小傅哥 | https://bugstack.cn
沉淀、分享、成长,专注于原创专题案例,以最易学习编程的方式分享知识,让自己和他人都能有所收获。目前已完成的专题有;Netty4.x实战专题案例、用Java实现JVM、基于JavaAgent的全链路监控、手写RPC框架、架构设计专题案例、源码分析等。
你用剑、我用刀,好的代码都很烧,望你不吝出招!
你见过这样的代码嘛?类似的呢?嗯,那么恭喜你被这个世界温柔以待!
if else
,并不是一个非常坏的关键字,只不过有人把他用坏了。尤其在接到产品需求如下这样;
日期 | 需求 | 紧急程度 | 程序员(话外音) |
---|---|---|---|
星期一.早上 | 猿哥哥,老板说要搞一下营销拉拉量,给男生女生发不同的优惠券,促活消费。 | 很紧急,下班就要 | 行吧,也不难,加下判断就上线 |
星期二.下午 | 小哥哥,咱们上线后非常好。要让咱们按照年轻、中年、成年,不同年龄加下判断,准确刺激消费。 | 超紧急,明天就要 | 也不难,加就加吧 |
星期三.晚上 | 喂,小哥哥!睡了吗!老板说咱们这次活动很成功,可以不可以在细分下,把单身、结婚、有娃的都加上不同判断。这样更能刺激用户消费。 | 贼紧急,最快上线。 | 已经意识到ifelse 越来越多了 |
星期四.凌晨 | 哇!小哥哥你们太棒了,上的真快。嘻嘻!有个小请求,需要调整下年龄段,因为现在学生处对象的都比较早,有对象的更容易买某某某东西。要改下值!辛苦辛苦! | 老板,在等着呢! | 一大片的值要修改,哎!这么多ifelse 了 |
星期五.半夜 | 歪歪喂!巴巴,坏了,怎么发的优惠券不对了,有客诉了,很多女生都来投诉。你快看看。老板,他… | (一头汗),哎,值粘错位置了! | 终究还是一个人扛下了所有 |
这样的场景你是否有遇到过呢,那么是产品给你代沟里去了,还是你把项目带沟里去了。可能会觉得,这东西这么着急要,我也没办法呀。其实不止你没有办法,是为了打下市场,让每一个人都很匆忙。只有合理的评估、铺垫、架设,才会不断满足业务需求、产品形态的变化。否则往后的路越来越难!
对于上面所提到的这种场景,在我们实际开发中是经常会遇到的。尤其是在一些;营销、风控、人群等,各种用户信息决策树关系时,都会出现这样的业务逻辑。而且对于一些较大场景是肯定不会直接硬编码if else
,因为太难以维护。当然除非你这东西就写一次用一次,下次不用了那无所谓。
接下来我们把上面的场景进行转换一种树结构图,依次来体现出这个需求的全貌,如下;
if else
你还能在自己掌握的技术栈中想到什么解决方案吗?接下来,我们会写出两种实现方式,用作比对。@Test
public void test_ifelse() {
Result result = null;
if ("男".equals(policy.getSex())) {
if (policy.getAge() < 18) {
if (policy.getUserSingle()) {
result = Result.buildResult("A", "红色A");
} else {
result = Result.buildResult("B", "红色B");
}
} else if (policy.getAge() >= 18 && policy.getAge() <= 30) {
if (policy.getUserMarry()) {
result = Result.buildResult("C", "红色C");
} else {
result = Result.buildResult("D", "红色D");
}
} else if (policy.getAge() > 30) {
if (policy.getUserParenting()) {
result = Result.buildResult("E", "红色E");
} else {
result = Result.buildResult("F", "红色F");
}
}
} else if ("女".equals(policy.getSex())) {
if (policy.getAge() < 18) {
if (policy.getUserSingle()) {
result = Result.buildResult("A", "黄色A");
} else {
result = Result.buildResult("B", "黄色B");
}
} else if (policy.getAge() >= 18 && policy.getAge() <= 30) {
if (policy.getUserMarry()) {
result = Result.buildResult("C", "黄色C");
} else {
result = Result.buildResult("D", "黄色D");
}
} else if (policy.getAge() > 30) {
if (policy.getUserParenting()) {
result = Result.buildResult("E", "黄色E");
} else {
result = Result.buildResult("F", "黄色F");
}
}
}
System.out.println("决策结果(IfElse):" + result);
}
if else
写出来还是没问题的,只不过写错不错就不一定了,毕竟一层套一层。这还算少的!关于规则引擎简单说呢就是,将你业务逻辑中那些行为规则流程变化的部分,分离出来。交给单独的规则引擎进行处理。最终你只需要按照约定提供配置和入参,就可以达到规则的执行结果。
Drools(JBoss Rules )具有一个易于访问企业策略、易于调整以及易于管理的开源业务规则引擎,符合业内标准,速度快、效率高。业务分析师或审核人员可以利用它轻松查看业务规则,从而检验是否已编码的规则执行了所需的业务规则。
上去就是一巴掌,然后在问为什么。好,先来把上面的代码用Drools
处理下,之后再解释。
源码获取
itstack-demo-drools-02
└── src
├── main
│ ├── java
│ │ └── org.itstack.demo
│ │ ├── model
│ │ │ └── Policy.java
│ │ └── Result.java
│ ├── resources
│ │ ├── META-INF
│ │ │ └── kmodule.xml
│ │ └── rules
│ │ └── tree.drl
│ └── webapp
│ └── index.html
└── test
└── java
└── org.itstack.demo.test
└── ApiTest.java
Drools
规则引擎的的基本工程,规则引擎使用的方式并不复杂,只要按照约定的方式进行设置即可。Policy.java & 定义决策属性,同时这也是Fact对象
public class Policy {
private String sex; // 性别;男、女
private Integer age; // 年龄
private Boolean userSingle; // 单身;是/否
private Boolean userMarry; // 结婚;是/否
private Boolean userParenting; // 育儿;是/否
...get/set
}
Result.java & 定义结果输出
public class Result {
private String code;
private String info;
}
META-INF/kmodule.xml & 配置文件
<kmodule xmlns="http://www.drools.org/xsd/kmodule">
<kbase name="rules">
<ksession name="all-rules"/>
kbase>
kmodule>
kbase
,分别对应drl
的规则文件kbase name="rules"
,name名称需要保证唯一rules/tree.drl & 规则文件
package rules;
import org.itstack.demo.model.Policy
import org.itstack.demo.Result;
global org.itstack.demo.Result res;
rule "红A"
when
Policy(sex == "男", age < 18, userSingle)
then
res.setResult("A","红色A");
end
rule "红B"
when
Policy(sex == "男", age < 18, !userSingle)
then
res.setResult("B","红色B");
end
rule "红C"
when
Policy(sex == "男", age >= 18, age <= 30, userMarry)
then
res.setResult("C","红色C");
end
rule "红D"
when
Policy(sex == "男", age >= 18, age <= 30, !userMarry)
then
res.setResult("D","红色D");
end
rule "红E"
when
Policy(sex == "男", age > 30, userParenting)
then
res.setResult("E","红色E");
end
rule "红F"
when
Policy(sex == "男", age > 30, !userParenting)
then
res.setResult("F","红色F");
end
rule "黄A"
when
Policy(sex == "女", age < 18, userSingle)
then
res.setResult("A","黄色A");
end
rule "黄B"
when
Policy(sex == "女", age < 18, !userSingle)
then
res.setResult("B","黄色B");
end
rule "黄C"
when
Policy(sex == "女", age >= 18, age <= 30, userMarry)
then
res.setResult("C","黄色C");
end
rule "黄D"
when
Policy(sex == "女", age >= 18, age <= 30, !userMarry)
then
res.setResult("D","黄色D");
end
rule "黄E"
when
Policy(sex == "女", age > 30, userParenting)
then
res.setResult("E","黄色E");
end
rule "黄F"
when
Policy(sex == "女", age > 30, !userParenting)
then
res.setResult("F","黄色F");
end
sex == "女", age > 30, !userParenting
,英文逗号隔开的是and的条件,相当你的且。当不完全是,因为在后续处理中,逗号的处理逻辑在drools是有优化的。global
全局引入。最后结尾end关键字。if else
吗。但千万不要这么觉得,因为这只是冰山一角。而且我们前面截图一个树形结构,而这个属性结构是可以自动化生成DRL
规则文件的。ApiTest.java & 单元测试中会设置Drools的启动过程
public class ApiTest {
private KieContainer kieContainer;
private Policy policy;
@Before
public void init() {
// 构建KieServices
KieServices kieServices = KieServices.Factory.get();
kieContainer = kieServices.getKieClasspathContainer();
policy = new Policy();
policy.setSex("男");
policy.setAge(16);
policy.setUserSingle(false);
policy.setUserMarry(false);
policy.setUserParenting(false);
System.out.println("决策请求:" + JSON.toJSONString(policy));
}
@Test
public void test_drools() {
KieSession kieSession = kieContainer.newKieSession("all-rules");
kieSession.insert(policy);
Result result = new Result();
kieSession.setGlobal("res", result);
int count = kieSession.fireAllRules();
System.out.println("Fire rule(s):" + count);
System.out.println("决策结果(Drools):" + result);
kieSession.dispose();
}
}
init() 初始化
KieServices.Factory.get();
,这个过程是比较耗费资源,实际业务使用中也不会频繁的构建。KieServices
中获取KieContainer
,用于给定KieModule的所有kiebase的容器。test_drools() 执行规则
kieSession.insert(policy);
kieSession.setGlobal("res", result);
,用于最终把结果输出kieSession.fireAllRules()
kieSession.dispose()
测试结果
决策请求:{"age":16,"sex":"男","userMarry":false,"userParenting":false,"userSingle":false}
Fire rule(s):1
决策结果(Drools):B|红色B
Drools 是用 Java 语言编写的开放源码规则引擎,使用 Rete 算法对所编写的规则求值。Drools 允许使用声明方式表达业务逻辑。可以使用非 XML 的本地语言编写规则,从而便于学习和理解。并且,还可以将 Java 代码直接嵌入到规则文件中,这令 Drools 的学习更加吸引人。
好!那么这样你就知道,Drools的核心内容是关于 Rete 算法的实现。接下来我们再来了解下 Rete。
为了解决生产式推理引擎效率底下的问题,Forgy 在1979年提出 Rete 算法,作为生产式系统的高效模式匹配算法。Rete 算法的初衷是:利用规则之间各个域的公用部分减少规则存储,同时保存匹配过程的临时结果以加快匹配速度。为了达到这种效果,算法将规则拆分,其中每个条件单元作为基本单位(节点)连接成一个数据辨别网络,然后将事实经过网络筛选并传播,最终所有条件都有事实匹配的规则被激活。
Rete 算法自从 1979 年提出以来,已经经历过各种改进与推广。除了对自身规则网络结构的优化外,对一些功能扩展如模糊推理、事件推理、并行化等也有很多研究。
混合逻辑符的处理
逻辑操作符(operators)是指注入and、or、not等,的逻辑运算符处理。
规则前件的重排序
规则前件顺序是指规则体哦啊见中的各个约束的排列顺序,它决定了条件链接操作的执行顺序,影响中间结果的大小,是决定规则匹配效率的关键因素。
索引方法
索引方法是指对 Rete 网络的节点建立当前节点对后继 的索引,在事实断言时可以通过索引快速找到对应的后继节 点而无需逐个查找。
处理其他逻辑
Rete 最初只是用于处理一阶布尔逻辑,目前有很多 Rete 的扩展被用来处理其他逻辑。
带时间信息的事件处理
Rete 通过事实来表达当前状态,但是很多应用包括一些事件流中的时间,在事件并行执行中起到关键作用。所以需要 Rete 算法对这些信息进行处理。
瑕疵数据与不确定性推理
快速变化数据与机器学习
除了数据瑕疵,对于变化剧烈的数据也成为Rete算法需要解决的问题。
Rete 算法从提出至今,性能提升问题一直是研究重点。多核多处理器问世后,将推理过程分配到不同机器上并行处理成为一种常见的效率提升方法