当前位置: 首页 > article >正文

设计并用Java实现一个简易的规则引擎

文章目录

  • 前言
  • 正文
    • 一、代码结构
    • 二、核心代码
      • 2.1 上下文数据接口 ContextData.java
      • 2.2 规则接口 Rule.java
      • 2.3 规则引擎接口 RuleEngine.java
      • 2.4 规则引擎上下文类 RuleEngineContext.java
      • 2.5 规则引擎默认实现类 DefaultRuleEngine.java
      • 2.6 执行时出错监听器接口 ExecuteOnErrorListener.java
      • 2.7 规则引擎执行模版类 RuleEngineExecuteTemplate.java
    • 三、测试业务
      • 3.1 新增折扣价格上下文实现类DiscountPriceContextData.java
      • 3.2 新增固定金额折扣规则实现类 FixedAmountDiscountRule.java
      • 3.3 新增百分比折扣规则实现类 PercentageDiscountRule.java
      • 3.4 新增满减折扣规则实现类 FullReductionDiscountRule.java
      • 3.5 编写测试类 Test.java
      • 3.6 测试类运行结果

前言

使用规则引擎可以很方便的实现一些比较复杂的业务逻辑。
本文介绍的简易版,是一个小的通用代码结构。

通过组装业务数据,创建执行模版,最终执行,获取到最终结果。

大家在复杂场景下,也可以选用Drools来实现。

Drools 是一个开源的业务规则管理系统,它提供了基于Java的规则引擎,用于实现复杂的事件处理和业务逻辑。Drools 支持声明式编程,允许开发人员将业务逻辑从应用程序代码中分离出来,这使得业务逻辑的管理和变更更加灵活和便捷。

正文

一、代码结构

在这里插入图片描述

二、核心代码

maven依赖文件:

    <properties>
        <java.version>17</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
    </dependencies>

2.1 上下文数据接口 ContextData.java

package com.song.tools.ruleengine.core;

/**
 * 上下文数据
 *
 * @author song tools
 */
public interface ContextData {

    /**
     * 校验上下文数据
     */
    default void validProperty() {
    }
}

2.2 规则接口 Rule.java

package com.song.tools.ruleengine.core;

/**
 * 规则接口
 *
 * @author song tools
 */
public interface Rule<ContextData extends com.song.tools.ruleengine.core.ContextData> {

    /**
     * 执行前
     *
     * @param contextData 上下文数据
     */
    default void before(ContextData contextData) {
        contextData.validProperty();
    }

    /**
     * 执行
     *
     * @param context 上下文
     */
    void execute(RuleEngineContext<ContextData> context);

    /**
     * 执行后
     *
     * @param contextData 上下文数据
     */
    default void after(ContextData contextData) {
    }
}

2.3 规则引擎接口 RuleEngine.java

package com.song.tools.ruleengine.core;

/**
 * 规则引擎
 *
 * @author song tools
 */
public interface RuleEngine<ContextData extends com.song.tools.ruleengine.core.ContextData> {

    /**
     * 注册规则
     *
     * @param ruleKey 规则key
     * @param rule    规则
     */
    void registerRule(String ruleKey, Rule<ContextData> rule);

    /**
     * 执行规则
     *
     * @param context 上下文
     */
    void executeRule(RuleEngineContext<ContextData> context) throws Exception;

    /**
     * 规则数量
     *
     * @return 规则数量
     */
    int ruleSize();
}

2.4 规则引擎上下文类 RuleEngineContext.java

package com.song.tools.ruleengine.core;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 规则引擎上下文
 *
 * @author song tools
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class RuleEngineContext<ContextData extends com.song.tools.ruleengine.core.ContextData> {

    /**
     * 上下文数据
     */
    private ContextData contextData;
}

2.5 规则引擎默认实现类 DefaultRuleEngine.java

package com.song.tools.ruleengine.core;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * 默认实现的规则引擎
 *
 * @author song tools
 */
public class DefaultRuleEngine<ContextData extends com.song.tools.ruleengine.core.ContextData> implements RuleEngine<ContextData> {

    private final Map<String, Rule<ContextData>> RULE_MAP;

    public DefaultRuleEngine() {
        RULE_MAP = new LinkedHashMap<>(16);
    }

    @Override
    public void registerRule(String ruleKey, Rule<ContextData> rule) {
        RULE_MAP.put(ruleKey, rule);
    }

    @Override
    public void executeRule(RuleEngineContext<ContextData> context) {
        for (Rule<ContextData> rule : RULE_MAP.values()) {
            rule.before(context.getContextData());
            rule.execute(context);
            rule.after(context.getContextData());
        }
    }

    @Override
    public int ruleSize() {
        return RULE_MAP.size();
    }
}

2.6 执行时出错监听器接口 ExecuteOnErrorListener.java

package com.song.tools.ruleengine.core;

/**
 * 执行时出错监听器
 *
 * @author song tools
 */
public interface ExecuteOnErrorListener<ContextData extends com.song.tools.ruleengine.core.ContextData> {

    void onError(ContextData contextData, Exception e);
}

2.7 规则引擎执行模版类 RuleEngineExecuteTemplate.java

package com.song.tools.ruleengine.core;

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 规则执行模版
 *
 * @author song tools
 */
@Getter
@Slf4j
public class RuleEngineExecuteTemplate<ContextData extends com.song.tools.ruleengine.core.ContextData> {

    /**
     * 规则引擎
     */
    private final RuleEngine<ContextData> ruleEngine;

    /**
     * 规则索引
     */
    private final AtomicInteger ruleIndex;

    /**
     * 执行失败监听器
     */
    private ExecuteOnErrorListener<ContextData> executeOnErrorListener;

    public RuleEngineExecuteTemplate() {
        this(new DefaultRuleEngine<>());
    }

    public RuleEngineExecuteTemplate(RuleEngine<ContextData> ruleEngine) {
        Objects.requireNonNull(ruleEngine, "ruleEngine is null");
        this.ruleIndex = new AtomicInteger(1);
        this.ruleEngine = ruleEngine;
    }


    /**
     * 注册规则
     *
     * @param rule 规则
     * @return this 当前模版对象
     */
    public RuleEngineExecuteTemplate<ContextData> registerRule(Rule<ContextData> rule) {
        String ruleKey = rule.getClass().getSimpleName();
        return this.registerRule(ruleKey, rule);
    }

    /**
     * 注册规则
     *
     * @param rules 规则列表
     * @return this 当前模版对象
     */
    public RuleEngineExecuteTemplate<ContextData> registerRule(List<Rule<ContextData>> rules) {
        for (Rule<ContextData> rule : rules) {
            this.registerRule(rule);
        }
        return this;
    }

    /**
     * 注册规则
     *
     * @param ruleKey 规则key
     * @param rule    规则
     * @return this 当前模版对象
     */
    public RuleEngineExecuteTemplate<ContextData> registerRule(String ruleKey, Rule<ContextData> rule) {
        ruleEngine.registerRule(ruleKey + ruleIndex.getAndIncrement(), rule);
        return this;
    }

    public RuleEngineExecuteTemplate<ContextData> registerExecuteOnErrorListener(ExecuteOnErrorListener<ContextData> executeOnErrorListener) {
        this.executeOnErrorListener = executeOnErrorListener;
        return this;
    }


    /**
     * 执行规则
     */
    public void execute(ContextData contextData) throws Exception {
        Objects.requireNonNull(contextData, "contextData is null");
        if (ruleEngine.ruleSize() == 0) {
            throw new IllegalArgumentException("ruleEngine not found register rule");
        }

        // 组装上下文
        RuleEngineContext<ContextData> context = new RuleEngineContext<>();
        context.setContextData(contextData);

        try {
            // 执行规则
            ruleEngine.executeRule(context);
        } catch (Exception e) {
            log.error("execute rule error", e);
            if (Objects.nonNull(executeOnErrorListener)) {
                executeOnErrorListener.onError(context.getContextData(), e);
            }
            throw e;
        }
    }
}


三、测试业务

假设现在需要灵活的计算单个商品的价格折扣相关的内容。

3.1 新增折扣价格上下文实现类DiscountPriceContextData.java

package com.song.tools.ruleengine;

import com.song.tools.ruleengine.core.ContextData;
import lombok.Data;

import java.math.BigDecimal;
import java.util.Objects;

/**
 * 折扣价格上下文(对于单件商品,简单场景的计算)
 *
 * @author song tools
 */
@Data
public class DiscountPriceContextData implements ContextData {

    /**
     * 临时价格(存在多个计算规则时,使用它进行传递计算的中间值)
     */
    private BigDecimal calculatedTempPrice;

    /**
     * 原价
     */
    private BigDecimal originalPrice;

    /**
     * 折扣后价格
     */
    private BigDecimal discountedPrice;

    @Override
    public void validProperty() {
        Objects.requireNonNull(originalPrice, "原价不能为空");
    }
}

3.2 新增固定金额折扣规则实现类 FixedAmountDiscountRule.java

package com.song.tools.ruleengine;

import com.song.tools.ruleengine.core.Rule;
import com.song.tools.ruleengine.core.RuleEngineContext;

import java.math.BigDecimal;
import java.math.RoundingMode;

public class FixedAmountDiscountRule implements Rule<DiscountPriceContextData> {
    private final BigDecimal discountAmount;

    public FixedAmountDiscountRule(BigDecimal discountAmount) {
        this.discountAmount = discountAmount;
    }

    @Override
    public void execute(RuleEngineContext<DiscountPriceContextData> context) {
        DiscountPriceContextData contextData = context.getContextData();
        // 临时价格
        BigDecimal calculatedTempPrice = contextData.getCalculatedTempPrice();
        // 原始价格
        BigDecimal originalPrice = contextData.getOriginalPrice();
        if (calculatedTempPrice != null) {
            originalPrice = calculatedTempPrice;
        }

        // 进行精确减法运算,并确保结果不会小于0
        BigDecimal discountedPrice = (originalPrice.subtract(discountAmount).max(BigDecimal.ZERO)).setScale(2, RoundingMode.HALF_UP);

        // 设置折扣后的价格
        contextData.setDiscountedPrice(discountedPrice);
        contextData.setCalculatedTempPrice(discountedPrice);
    }

    @Override
    public void before(DiscountPriceContextData contextData) {
        Rule.super.before(contextData);
    }

    @Override
    public void after(DiscountPriceContextData contextData) {
        Rule.super.after(contextData);
    }
}

3.3 新增百分比折扣规则实现类 PercentageDiscountRule.java

package com.song.tools.ruleengine;

import com.song.tools.ruleengine.core.Rule;
import com.song.tools.ruleengine.core.RuleEngineContext;

import java.math.BigDecimal;
import java.math.RoundingMode;

public class PercentageDiscountRule implements Rule<DiscountPriceContextData> {

    private static final BigDecimal ONE_HUNDRED = new BigDecimal("100");
    private final BigDecimal discountPercentage;

    public PercentageDiscountRule(BigDecimal discountPercentage) {
        this.discountPercentage = discountPercentage;
    }

    @Override
    public void execute(RuleEngineContext<DiscountPriceContextData> context) {
        DiscountPriceContextData contextData = context.getContextData();
        // 临时价格
        BigDecimal calculatedTempPrice = contextData.getCalculatedTempPrice();
        // 原始价格
        BigDecimal originalPrice = contextData.getOriginalPrice();
        if (calculatedTempPrice != null) {
            originalPrice = calculatedTempPrice;
        }

        // 计算折扣率
        BigDecimal discountRate = discountPercentage.divide(ONE_HUNDRED, 2, RoundingMode.HALF_UP);

        // 计算折扣后的价格(四舍五入,保留2位小数)
        BigDecimal discountedPrice = originalPrice.multiply(discountRate).setScale(2, RoundingMode.HALF_UP);

        contextData.setDiscountedPrice(discountedPrice);
        contextData.setCalculatedTempPrice(discountedPrice);
    }

    @Override
    public void before(DiscountPriceContextData contextData) {
        Rule.super.before(contextData);
    }

    @Override
    public void after(DiscountPriceContextData contextData) {
        Rule.super.after(contextData);
    }
}

3.4 新增满减折扣规则实现类 FullReductionDiscountRule.java

package com.song.tools.ruleengine;

import com.song.tools.ruleengine.core.Rule;
import com.song.tools.ruleengine.core.RuleEngineContext;

import java.math.BigDecimal;
import java.math.RoundingMode;

/**
 * 满减折扣规则
 *
 * @author song tools
 */
public class FullReductionDiscountRule implements Rule<DiscountPriceContextData> {

    /**
     * 满减金额,满xx金额数
     */
    private final BigDecimal fullReductionBoundAmount;

    /**
     * 满减折扣金额,减去xx金额数
     */
    private final BigDecimal fullReductionDiscountAmount;

    public FullReductionDiscountRule(BigDecimal fullReductionBoundAmount, BigDecimal fullReductionDiscountAmount) {
        this.fullReductionBoundAmount = fullReductionBoundAmount;
        this.fullReductionDiscountAmount = fullReductionDiscountAmount;
    }

    public void execute(RuleEngineContext<DiscountPriceContextData> context) {
        DiscountPriceContextData contextData = context.getContextData();
        // 临时价格
        BigDecimal calculatedTempPrice = contextData.getCalculatedTempPrice();
        // 原始价格
        BigDecimal originalPrice = contextData.getOriginalPrice();
        if (calculatedTempPrice != null) {
            originalPrice = calculatedTempPrice;
        }

        // 比较金额是否满足满减条件
        if (originalPrice.compareTo(fullReductionBoundAmount) >= 0) {
            BigDecimal discountedPrice = originalPrice.subtract(fullReductionDiscountAmount).setScale(2, RoundingMode.HALF_UP);
            contextData.setDiscountedPrice(discountedPrice);
            contextData.setCalculatedTempPrice(discountedPrice);
        }
    }
}

3.5 编写测试类 Test.java

编排不同的规则,不通的顺序,验证最终的执行结果。

package com.song.tools.ruleengine;

import com.song.tools.ruleengine.core.DefaultRuleEngine;
import com.song.tools.ruleengine.core.RuleEngineContext;
import com.song.tools.ruleengine.core.RuleEngineExecuteTemplate;

import java.math.BigDecimal;
import java.math.RoundingMode;

/**
 * 测试规则执行
 *
 * @author song tools
 */
public class Test {

    public static void main(String[] args) throws Exception {
        System.out.println("----------------test1----------------");
        test1();
        System.out.println("----------------test1----------------");

        System.out.println("----------------test2----------------");
        test2();
        System.out.println("----------------test2----------------");
    }


    private static void test1() {
        // 创建规则引擎
        DefaultRuleEngine<DiscountPriceContextData> ruleEngine = new DefaultRuleEngine<>();
        // 创建规则+注册规则
        ruleEngine.registerRule("percentageDiscountRule1", new PercentageDiscountRule(BigDecimal.valueOf(95)));
        ruleEngine.registerRule("fixedAmountDiscountRule1", new FixedAmountDiscountRule(BigDecimal.valueOf(12.34)));
        ruleEngine.registerRule("percentageDiscountRule2", new PercentageDiscountRule(BigDecimal.valueOf(80)));
        ruleEngine.registerRule("fixedAmountDiscountRule2", new FixedAmountDiscountRule(BigDecimal.valueOf(12.4)));

        // 创建上下文数据
        RuleEngineContext<DiscountPriceContextData> context = new RuleEngineContext<>();
        DiscountPriceContextData discountPriceContextData = new DiscountPriceContextData();
        discountPriceContextData.setOriginalPrice(BigDecimal.valueOf(100));
        context.setContextData(discountPriceContextData);

        // 执行规则引擎
        ruleEngine.executeRule(context);

        // 打印执行结果
        System.out.println(discountPriceContextData.getDiscountedPrice());

        System.out.println(BigDecimal.valueOf(100).multiply(BigDecimal.valueOf(0.95)).subtract(BigDecimal.valueOf(12.34)));
        System.out.println(BigDecimal.valueOf(82.66).multiply(BigDecimal.valueOf(0.8).setScale(2, RoundingMode.HALF_UP)).subtract(BigDecimal.valueOf(12.4)).setScale(2, RoundingMode.HALF_UP));
    }

    private static void test2() throws Exception {
        // 创建上下文数据
        DiscountPriceContextData discountPriceContextData = new DiscountPriceContextData();
        discountPriceContextData.setOriginalPrice(BigDecimal.valueOf(100));

        // 创建规则引擎执行模板
        RuleEngineExecuteTemplate<DiscountPriceContextData> ruleEngineExecuteTemplate = new RuleEngineExecuteTemplate<>();

        // 注册规则&执行规则
        ruleEngineExecuteTemplate.registerRule(new PercentageDiscountRule(BigDecimal.valueOf(95))) // 95折
                .registerRule(new FixedAmountDiscountRule(BigDecimal.valueOf(12.34))) // 减12.34元
                .registerRule(new PercentageDiscountRule(BigDecimal.valueOf(80))) // 80折
                .registerRule(new FullReductionDiscountRule(BigDecimal.valueOf(50), BigDecimal.valueOf(10))) // 满50减10
                .registerRule(new FixedAmountDiscountRule(BigDecimal.valueOf(12.4))) // 减12.4元
                .registerRule(new FullReductionDiscountRule(BigDecimal.valueOf(40), BigDecimal.valueOf(19.9))) // 满40减19.9元
                .execute(discountPriceContextData);

        // 打印最终结果
        System.out.println(discountPriceContextData.getDiscountedPrice());
    }
}

3.6 测试类运行结果

----------------test1----------------
53.73
82.66
53.73
----------------test1----------------
----------------test2----------------
23.83
----------------test2----------------


http://www.kler.cn/news/295027.html

相关文章:

  • Python | Leetcode Python题解之第392题判断子序列
  • 【leetcode详解】爬楼梯:DP入门典例(附DP通用思路 同类进阶练习)
  • 使用Protocol Buffers传输数据
  • 在vscode中用virtual env的方法
  • git如何灵活切换本地账号对应远程github的两个账号
  • 代码随想录:279. 完全平方数
  • 如何在Selenium中使用Chrome进行网络限速
  • ComfyUI+Krea免费利用AI制作网站萌宠IP,五步搞定制作AI萌宠
  • React 响应事件
  • 【Godot4.3】多边形的斜线填充效果基础实现
  • 在Ubuntu 20.04上安装Nginx的方法
  • 懒人笔记-opencv4.8.0篇
  • 【详解 Java 注解】
  • 一些数学经验总结——关于将原一元二次函数增加一些限制条件后最优结果的对比(主要针对公平关切相关的建模)
  • 分数阶微积分MATLAB计算
  • 将你的github仓库设置为web代理
  • Java零基础-如何在分布式系统中进行日志管理?
  • 【鸿蒙】HarmonyOS NEXT星河入门到实战1-开发环境准备
  • Vulnhub:Dr4g0n b4ll 1
  • Qt/C++开源项目 TCP客户端调试助手(源码分享+发布链接下载)
  • <class ‘pyspark.sql.dataframe.DataFrame‘>
  • Eureka原理与实践:构建高可用微服务架构的基石
  • MCU5.51单片机的最小系统
  • IDEA git提交时如何忽略某个文件或文件夹
  • 任务执行拓扑排序(华为od机考题)
  • Elasticsearch - SpringBoot 索引与文档相关demo
  • Spring Boot 部署方案!打包 + Shell 脚本详解
  • 【知识分享】MQTT实战-使用mosquitto客户端连接emqx服务器
  • 【人工智能】Transformers之Pipeline(十五):总结(summarization)
  • ubuntu上通过openvswitch卸载实现roce over vxlan