为什么需要规则引擎?

在我们开始编写代码之前,先来聊聊为什么你需要一个规则引擎:

  • 将业务逻辑与核心应用代码分离
  • 允许非技术人员在不进行完整部署的情况下调整规则
  • 让你的系统更能适应变化(相信我,变化总会到来)
  • 提高可维护性和可测试性

现在我们都在同一频道上了,让我们动手实践吧!

核心组件

我们的规则引擎将由三个主要部分组成:

  1. 规则:单个业务规则
  2. 规则引擎:处理规则的大脑
  3. 事实:规则将操作的数据

让我们逐一分解这些部分。

1. 规则接口

首先,我们需要为规则定义一个接口:


public interface Rule {
    boolean evaluate(Fact fact);
    void execute(Fact fact);
}

简单吧?每个规则都知道如何根据一个事实进行评估,以及在匹配时该做什么。

2. 事实类

接下来,让我们定义我们的事实类:


public class Fact {
    private Map attributes = new HashMap<>();

    public void setAttribute(String name, Object value) {
        attributes.put(name, value);
    }

    public Object getAttribute(String name) {
        return attributes.get(name);
    }
}

这种灵活的结构允许我们向事实中添加任何类型的数据。可以将其视为你的业务数据的键值存储。

3. 规则引擎类

现在是我们的主角,规则引擎:


public class RuleEngine {
    private List rules = new ArrayList<>();

    public void addRule(Rule rule) {
        rules.add(rule);
    }

    public void process(Fact fact) {
        for (Rule rule : rules) {
            if (rule.evaluate(fact)) {
                rule.execute(fact);
            }
        }
    }
}

这就是魔法发生的地方。引擎遍历所有规则,根据需要评估和执行它们。

整合一切

现在我们有了核心组件,让我们通过一个简单的例子看看它们如何协同工作。假设我们正在为一个电子商务平台构建一个折扣系统。


public class DiscountRule implements Rule {
    @Override
    public boolean evaluate(Fact fact) {
        Integer totalPurchases = (Integer) fact.getAttribute("totalPurchases");
        return totalPurchases != null && totalPurchases > 1000;
    }

    @Override
    public void execute(Fact fact) {
        fact.setAttribute("discount", 10);
    }
}

// 使用
RuleEngine engine = new RuleEngine();
engine.addRule(new DiscountRule());

Fact customerFact = new Fact();
customerFact.setAttribute("totalPurchases", 1500);

engine.process(customerFact);

System.out.println("Discount: " + customerFact.getAttribute("discount") + "%");

在这个例子中,如果客户的购买总额超过1000美元,他们将获得10%的折扣。简单、有效且易于修改。

进一步扩展

现在我们掌握了基础知识,让我们探索一些增强规则引擎的方法:

1. 规则优先级

为规则添加优先级字段,并在引擎中对其进行排序:


public interface Rule {
    int getPriority();
    // ... 其他方法
}

public class RuleEngine {
    public void addRule(Rule rule) {
        rules.add(rule);
        rules.sort(Comparator.comparingInt(Rule::getPriority).reversed());
    }
    // ... 其他方法
}

2. 规则链

允许规则触发其他规则:


public class RuleEngine {
    public void process(Fact fact) {
        boolean ruleExecuted;
        do {
            ruleExecuted = false;
            for (Rule rule : rules) {
                if (rule.evaluate(fact)) {
                    rule.execute(fact);
                    ruleExecuted = true;
                }
            }
        } while (ruleExecuted);
    }
}

3. 规则组

将规则组织成组以便更好地管理:


public class RuleGroup implements Rule {
    private List rules = new ArrayList<>();

    public void addRule(Rule rule) {
        rules.add(rule);
    }

    @Override
    public boolean evaluate(Fact fact) {
        return rules.stream().anyMatch(rule -> rule.evaluate(fact));
    }

    @Override
    public void execute(Fact fact) {
        rules.forEach(rule -> {
            if (rule.evaluate(fact)) {
                rule.execute(fact);
            }
        });
    }
}

性能考虑

虽然我们的规则引擎已经相当不错,但总有优化的空间:

  • 使用更高效的数据结构存储规则(例如,用树来处理层次结构规则)
  • 为频繁访问的事实或规则评估实现缓存
  • 考虑对大型规则集进行并行处理

可维护性提示

为了防止你的规则引擎变成一头野兽:

  • 详细记录每个规则
  • 为你的规则实现版本控制
  • 为非技术用户创建一个友好的界面来修改规则
  • 定期审核和清理过时的规则

总结

这就是全部内容!一个精简、高效的规则引擎,大约150行代码。它灵活、可扩展,可能会在下一次业务逻辑危机中拯救你。记住,一个好的规则引擎的关键是保持简单,同时在需要时允许复杂性。

在你离开之前,思考一下:这个规则引擎的概念如何应用于你代码库的其他领域?它能否简化你当前项目中的复杂决策过程?

祝编码愉快,愿你的业务逻辑始终动态而出色!

“简单是终极的复杂。” - 达芬奇(他当时不是在谈论编码,但他完全可以这么说)

附言:如果你想深入研究规则引擎,可以查看一些开源项目,比如Easy RulesDrools。它们可能会给你一些将自制引擎提升到新水平的想法!