规则引擎根据多个条件过滤多个输入对象

时间:2017-06-26 17:00:45

标签: java oop object design-patterns

我想设计一个规则引擎来过滤传入的对象,如下所示:

一开始,我有三个不同的类:A,B,C。类的列表是动态的,即:如果稍后添加D和E,我想扩展它以使用D,E等类。

public class A {
   int a1;
   String a2;
   boolean a3;
   Date a4;
   List<String> a5;
   ...
}

public class B {
   String b1;
   boolean b2;
   Date b3;
   long b4;
   ...
}

public class C {
   String c1;
   boolean c2;
   Date c3;
   long c4;
   ...
}

将会有我的规则引擎过滤的A,B或C类不同的对象。

用户可以根据一个类的每个成员变量可能具有的一组预定义操作来定义不同的规则。

一些操作的例子:

  • a1可以执行以下操作:&gt; =,&lt; =,或者在某个范围之间等等。
  • a2可以执行以下操作:不是,或是等等。
  • a3只能是真或假。
  • a4可以是:在某个日期之前,某个日期之后,或之间等等。
  • a5可以是:列表中是否存在,等等。

A类对象的一些示例规则是:

  • a1介于0和100之间,a2不是“ABC”,a3为假,a4在今天之前,a5包含“cdf”等。
  • a2是“abc”,a3是真,a4在某个日期范围之间。

一颗子弹是一条规则。在规则中,可以有一个或多个标准(多个标准,即AND一起)。

每个标准都由一个成员变量定义,该变量具有我可以对该变量应用的操作。

规则引擎必须能够处理用户为A,B或C类的每个对象定义的规则。对于每个进入的规则(A Rule,B Rule或C Rule),返回将是一个列表与指定规则匹配的对象。

我可以创建Criterion,Criteria,ARule,BRule,CRule,Operation对象等;我可以采用蛮力的做事方式;但那将是很多if ... else ......陈述。

我很欣赏任何设计模式/设计方法的所有想法,我可以使用它来使其清洁和可扩展。

非常感谢你的时间。

1 个答案:

答案 0 :(得分:1)

听起来像规则实际上是由{em>和 - 其他谓词构成的Predicate。使用Java 8,您可以让用户定义属性的谓词:

Predicate<A> operationA1 = a -> a.getA1() >= 10;           // a1 is int
Predicate<A> operationA2 = a -> a.getA2().startsWith("a"); // a2 is String
Predicate<A> operationA3 = a -> a.getA3(); // == true;        a3 is boolean

Predicate<A> ruleA = operationA1.and(operationA2).and(operationA3);

现在,您可以流式传输List<A>,过滤并收集到新列表:

List<A> result = listOfA.stream()
    .filter(ruleA)
    .collect(Collectors.toList());

您可以对BC使用类似的方法。

现在,有几种方法可以抽象出所有这些。这是一个可能的解决方案:

public static <T, P> Predicate<T> operation(
        Function<T, P> extractor, 
        Predicate<P> condition) {

    return t -> condition.test(extractor.apply(t));
}

此方法基于Function创建一个谓词(代表您的某个操作),该谓词将从ABC中提取属性(或未来的课程)和Predicate以上的财产。

对于我上面显示的相同示例,您可以这样使用它:

Predicate<A> operation1A = operation(A::getA1, p -> p >= 10);
Predicate<A> operation2A = operation(A::getA2, p -> p.startsWith("a"));
Predicate<A> operation3A = operation(A::getA3, p -> p); // p == true ?

但是,由于该方法是通用的,您还可以将其用于B

的实例
Predicate<B> operation1B = operation(B::getA1, p -> p.startsWith("z"));
Predicate<B> operation2B = operation(B::getA2, p -> !p); // p == false ?
Predicate<B> operation3B = operation(B::getA3, p -> p.before(new Date()));

现在您已经定义了一些操作,您需要一种通用的方法来从操作中创建规则:

public static <T> Predicate<T> rule(Predicate<T>... operations) {
    return Arrays.stream(operations).reduce(Predicate::and).orElse(t -> true);
}

此方法通过创建规则来执行给定的操作。它首先从给定数组创建一个流,然后通过将Predicate#and方法应用于操作来减少此流。您可以查看Arrays#streamStream#reduceOptional#orElse文档了解详情。

因此,要为A创建规则,您可以执行以下操作:

Predicate<A> ruleA = rule(
    operation(A::getA1, p -> p >= 10),
    operation(A::getA2, p -> p.startsWith("a")),
    operation(A::getA3, p -> p));

List<A> result = listOfA.stream()
    .filter(ruleA)
    .collect(Collectors.toList());