天天看点

这种场景你还写ifelse代码,你跟孩子坐一桌去吧

作者:小傅哥

博客:https://bugstack.cn - 包含: Java 基础,面经手册,Netty4.x,手写Spring,用Java实现JVM,重学Java设计模式,SpringBoot中间件开发,IDEA插件开发,DDD系统架构项目开发,字节码编程...

沉淀、分享、成长,让自己和他人都能有所收获!

一、前言

这种场景你还写ifelse代码,你跟孩子坐一桌去吧

你见过这样的代码嘛?类似的呢?嗯,那么恭喜你被这个世界温柔以待!

if else,并不是一个非常坏的关键字,只不过有人把他用坏了。尤其在接到产品需求如下这样;

这种场景你还写ifelse代码,你跟孩子坐一桌去吧

这样的场景你是否有遇到过呢,那么是产品给你代沟里去了,还是你把项目带沟里去了。可能会觉得,这东西这么着急要,我也没办法呀。其实不止你没有办法,是为了打下市场,让每一个人都很匆忙。只有合理的评估、铺垫、架设,才会不断满足业务需求、产品形态的变化。否则往后的路越来越难!

二、场景

对于上面所提到的这种场景,在我们实际开发中是经常会遇到的。尤其是在一些;营销、风控、人群等,各种用户信息决策树关系时,都会出现这样的业务逻辑。而且对于一些较大场景是肯定不会直接硬编码if else,因为太难以维护。当然除非你这东西就写一次用一次,下次不用了那无所谓。

接下来我们把上面的场景进行转换一种树结构图,依次来体现出这个需求的全貌,如下;

这种场景你还写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

关于规则引擎简单说呢就是,将你业务逻辑中那些行为规则流程变化的部分,分离出来。交给单独的规则引擎进行处理。最终你只需要按照约定提供配置和入参,就可以达到规则的执行结果。

Drools(JBoss Rules )具有一个易于访问企业策略、易于调整以及易于管理的开源业务规则引擎,符合业内标准,速度快、效率高。业务分析师或审核人员可以利用它轻松查看业务规则,从而检验是否已编码的规则执行了所需的业务规则。

上去就是一巴掌,然后在问为什么。好,先来把上面的代码用Drools处理下,之后再解释。

1. 环境配置

  1. jdk1.8.0
  2. idea + maven3.x
  3. drools 7.32.0.Final
  4. 案例源码下载,关注公众号:bugstack虫洞栈 回复:源码获取
  5. 可视化流程图解决方案;flowdiagram.itstack.org

2. 工程结构

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
           
  • 关于案例中出现的代码,可以通过关注公众号获取:bugstack虫洞栈,回复关键字**<获取源码>**
  • 以上是我们关于使用Drools规则引擎的的基本工程,规则引擎使用的方式并不复杂,只要按照约定的方式进行设置即可。

3. 代码讲解

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 & 配置文件
<?xml version="1.0" encoding="utf-8" ?>
<kmodule xmlns="http://www.drools.org/xsd/kmodule">

    <kbase name="rules">
        <ksession name="all-rules"/>
    </kbase>

</kmodule>
           
  • kmodule 可以包含多个kbase,分别对应drl的规则文件
  • kbase name="rules",name名称需要保证唯一
  • kbase下面可以有一个或多个ksession,ksession的name属性必须设置,且必须唯一
  • kbase的default属性,表示当前KieBase是不是默认的,如果是默认的则不用名称就可以查找到该KieBase,但每个module最多只能有一个默认KieBase
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  
           
  • rule 规则名称、when then end 一套组合拳,什么条件下输出什么结果
  • sex == "女", age > 30, !userParenting,英文逗号隔开的是and的条件,相当你的且。当不完全是,因为在后续处理中,逗号的处理逻辑在drools是有优化的。
  • then中处理结果,将结果信息返回,这个结果使用是我们设置的一个global全局引入。最后结尾end关键字。
  • 也许你会觉得这不是很像你的if else吗。但千万不要这么觉得,因为这只是冰山一角。而且我们前面截图一个树形结构,而这个属性结构是可以自动化生成DRL规则文件的。

4. 测试执行

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() 初始化

  1. 在初始化方法中,构建KieServices.Factory.get();,这个过程是比较耗费资源,实际业务使用中也不会频繁的构建。
  2. 从KieServices中获取KieContainer,用于给定KieModule的所有kiebase的容器。
  3. 设置FACT对象,其实就是你的决策对象的一些条件值。

test_drools() 执行规则

  1. 获取kmodule.xml中配置中名称为all-rules的session,默认为有状态的。
  2. 设置决策对象kieSession.insert(policy);
  3. 设置全局对象 kieSession.setGlobal("res", result);,用于最终把结果输出
  4. 开始执行规则kieSession.fireAllRules()
  5. 最终输出结果,到最后释放资源kieSession.dispose()

测试结果

决策请求:{"age":16,"sex":"男","userMarry":false,"userParenting":false,"userSingle":false}
Fire rule(s):1
决策结果(Drools):B|红色B
           
  • 在测试过程中可以尝试修改入参信息,以此验证不同的结果。

五、Rete 算法了解

Drools 是用 Java 语言编写的开放源码规则引擎,使用 Rete 算法对所编写的规则求值。Drools 允许使用声明方式表达业务逻辑。可以使用非 XML 的本地语言编写规则,从而便于学习和理解。并且,还可以将 Java 代码直接嵌入到规则文件中,这令 Drools 的学习更加吸引人。

好!那么这样你就知道,Drools的核心内容是关于 Rete 算法的实现。接下来我们再来了解下 Rete。

为了解决生产式推理引擎效率底下的问题,Forgy 在1979年提出 Rete 算法,作为生产式系统的高效模式匹配算法。Rete 算法的初衷是:利用规则之间各个域的公用部分减少规则存储,同时保存匹配过程的临时结果以加快匹配速度。为了达到这种效果,算法将规则拆分,其中每个条件单元作为基本单位(节点)连接成一个数据辨别网络,然后将事实经过网络筛选并传播,最终所有条件都有事实匹配的规则被激活。

Rete 算法自从 1979 年提出以来,已经经历过各种改进与推广。除了对自身规则网络结构的优化外,对一些功能扩展如模糊推理、事件推理、并行化等也有很多研究。

1. 结构优化

  1. 混合逻辑符的处理 逻辑操作符(operators)是指注入and、or、not等,的逻辑运算符处理。
  2. 规则前件的重排序 规则前件顺序是指规则体哦啊见中的各个约束的排列顺序,它决定了条件链接操作的执行顺序,影响中间结果的大小,是决定规则匹配效率的关键因素。
  3. 索引方法 索引方法是指对 Rete 网络的节点建立当前节点对后继 的索引,在事实断言时可以通过索引快速找到对应的后继节 点而无需逐个查找。

2. 功能扩展

  1. 处理其他逻辑 Rete 最初只是用于处理一阶布尔逻辑,目前有很多 Rete 的扩展被用来处理其他逻辑。
  2. 带时间信息的事件处理 Rete 通过事实来表达当前状态,但是很多应用包括一些事件流中的时间,在事件并行执行中起到关键作用。所以需要 Rete 算法对这些信息进行处理。

3. 特殊数据的推理

  1. 瑕疵数据与不确定性推理 不正确性不精准性不一致性
  2. 快速变化数据与机器学习 除了数据瑕疵,对于变化剧烈的数据也成为Rete算法需要解决的问题。

4. 并行化

Rete 算法从提出至今,性能提升问题一直是研究重点。多核多处理器问世后,将推理过程分配到不同机器上并行处理成为一种常见的效率提升方法

六、总结

  • 优秀的产品、优秀的研发,从来不只是传话筒也不是工具机器。而是有灵魂的工匠,需要有谋有段,决策、远见。
  • Drools的使用还不止是这一点,他还丰富的很,我们本章节主要是一个开篇,后续会继续完善。关于工程代码可以关注公众号(bugstack虫洞栈)进行获取。
  • 只有你的技术识栈足够的全面,才能让你在遇到一个问题的时候,有N中的方案。但学习一定是自己的事,无论是忙与闲,都要让自己充充电。娱乐不是不可以,只不过要适当的控制下自己。如果你控制不住自己,就会有别人控制你