基于动态条件的决策模型

时间:2018-11-03 10:48:15

标签: java design-patterns antlr rule-engine mvel

我有一个xml文件,其中包含要根据元数据JSON 值数据JSON 输入进行检查的条件。

  

示例元数据JSON输入可以

{
"max_cash":789,
"min_cash":12,
"start_date":"28 Oct 2018",
"end_date":"02 Dec 2018",
"allowed_cities":"3,4,5,6"
} 
  

样本值数据JSON可以

{
"cash":34,
"order_date":"03 Nov 2018",
"city_id":"5,4"
}

每个条件都可以构成一个表达式,并具有以下属性

  • param_holder-这是表达式的左术语。它只是一个键,其值在元数据JSON中。
  • param_value_holder-这是表达的正确术语。它只是一个值在值数据JSON中的键。
  • operator-要在左右项上执行的运算符。例如<,>,<=,> =,<>,==,CONTAINS,NOT_CONTAINS,CONTAINS_AT_LEAST_ONE等。
  • param_type-左右项的数据类型。例如NUMBER,DATE,TIME,STRING,LIST。
  

示例xml如下

<condtion id="1" param_type="NUMBER" param_holder="max_cash" operator=">=" param_value_holder="cash" next_condition="2"></condtion>
<condtion id="2" param_type="NUMBER" param_holder="min_cash" operator="<=" param_value_holder="cash" next_condition="3"></condtion>
<condtion id="3" param_type="DATE" param_holder="start_date" operator="<=" param_value_holder="order_date" next_condition="4"></condtion>
<condtion id="4" param_type="DATE" param_holder="end_date" operator=">=" param_value_holder="order_date" next_condition="5"></condtion>
<condtion id="5" param_type="LIST" param_holder="allowed_cities" operator="CONTAINS" param_value_holder="city_id" next_condition="-1">

我正在使用JAVA,这里几乎所有内容都是动态的,可以随时添加,修改和删除。将来可能还会有更多的运算符,数据类型等。 我正在寻找一种解决方案,使人们可以添加任何条件而无需重新部署代码。仅当元数据JSON更改时,才应进行代码部署。 我读到有关ANTLR的文章,它认为这将使解决方案无效,并且语法和解析器的创建和维护对开发人员来说是开销。 我用Factory Pattern提出了另一种解决方案,其中Operator工厂将输入作为操作符,并返回相应的Operator对象,该对象进一步将左项,右项和参数类型作为参数,并根据特定的参数类型评估条件。

public abstract class Operator {

    public abstract boolean evaluate(String leftTerm, String rightTerm, String param_type) throws ParseException;

}

public class GTEOperator extends Operator {
    @Override
    public boolean evaluate(String leftTerm, String rightTerm, String param_type) throws ParseException {
        switch (param_type)
        {
            case "DATE":
                return new SimpleDateFormat().parse(leftTerm).after(new SimpleDateFormat().parse(rightTerm));
            case "NUMBER":
                return Integer.parseInt(leftTerm) >=Integer.parseInt(rightTerm);
            default:
                return true;
        }
    }
}

public class ContainsOperator extends Operator {
    @Override
    public boolean evaluate(String leftTerm, String rightTerm, String param_type) throws ParseException {
        return Arrays.asList(leftTerm.split(",")).contains(rightTerm);
    }
} 

此解决方案不是动态的,并且涉及大量代码重写。 有人可以用一种更好的,可扩展的和面向对象的方法来帮助我。

1 个答案:

答案 0 :(得分:0)

在这里我建议一些更明智的功能性方法。

首先,摆脱next_condition。由于附加功能是可交换的(即booleanValue1 && boleanValue2始终等于booleanValue2 && booleanValue1),因此您根本不在乎评估的特定顺序

其次,您最好将每个条件映射到a ..谓词。函数,使用要验证的对象,并在最简单的情况或更复杂的情况下返回单个true/false(例如一系列有意义的错误以及您可能需要的任何麻烦)。

只要有了这些谓词,就可以将它们自由合并(减少,折叠,聚合-本质上相同的功能有很多名称)将它们合并为一个:决定,您是否愿意懒惰或急切的评估策略,要么生成另一个谓词,要么即时计算它们,然后吐出结果布尔值。因此,您的“客户代码”接收到一个ToBeValidateObject -> Boolean函数,很好地封装了所有底层细节

最后,您肯定需要某种工厂才能根据指定的操作类型来产生有效的谓词。那就是您的主要扩展点:引入新的运算符或重新定义旧的运算符应该与修改Factory.Create(...)方法一样简单。确保有IFactory接口以避免静态不可单元测试的代码依赖关系。在内部,它可能像HashTable一样简单,由适当的字符串/字符数组作为键。

希望有帮助。让我知道是否需要更多代码示例。