在Java中设计规则引擎的有效设计模式/风格是什么?



我正在用Java实现一个规则引擎。我的规则引擎预定义了一个独立规则和规则集的列表。这里的规则只是一条逻辑。规则集将这些简单规则组合成有序集。

我是一个体面的java开发人员,但不是一个大师。我的同事建议我做两个设计。我对两种设计都不满意,所以才提出这个问题。

项目中的规则示例:假设输入是位于美国的位置,例如,Santa Barbara, CA, USA或OH, US,通常采用一些具有城市,州和国家字段的良好定义格式。然后我可以有一些规则如下:

规则1: City not null
规则2: State not null
规则3: Country = US或USA
规则4:状态长度等于2

我的项目中的规则集示例:

RULESET:有效位置这个规则集是上面定义的规则的有序集合。

我实现的两个设计模板如下:

设计1:在匿名内部类中使用Enum

Rule.java

public interface Rule {
    public Object apply(Object object);
}

NlpRule.java

public enum NlpRule {
    CITY_NOT_NULL(new Rule() {
        @Override
        public Object apply(Object object) {
            String location = (String) object;
            String city = location.split(",")[0];
            if (city != null) {
                return true;
            }
            return false;
        }
    }),
    STATE_NOT_NULL(new Rule() {
        @Override
        public Object apply(Object object) {
            String location = (String) object;
            String state = location.split(",")[1];
            if (state != null) {
                return true;
            }
            return false;
        }
    }),
    COUNTRY_US(new Rule() {
        @Override
        public Object apply(Object object) {
            String location = (String) object;
            String country = location.split(",")[2];
            if (country.equals("US") || country.equals("USA")) {
                return true;
            }
            return false;
        }
    }),
    STATE_ABBREVIATED(new Rule() {
        @Override
        public Object apply(Object object) {
            String location = (String) object;
            String state = location.split(",")[1];
            if (state.length() == 2) {
                return true;
            }
            return false;
        }
    });
    private Rule rule;
    NlpRule(Rule rule) {
        this.rule = rule;
    }
    public Object apply(Object object) {
        return rule.apply(object);
    }
}

RuleSet.java

public class RuleSet {
    private List<NlpRule> rules;
    public RuleSet() {
        rules = new ArrayList<NlpRule>();
    }
    public RuleSet(List<NlpRule> rules) {
        this.rules = rules;
    }
    public void add(NlpRule rule) {
        rules.add(rule);
    }
    public boolean apply(Object object) throws Exception {
        boolean state = false;
        for (NlpRule rule : rules) {
            state = (boolean) rule.apply(object);
        }
        return state;
    }
}

RuleSets.java

public class RuleSets {
    private RuleSets() {
    }
    public static RuleSet isValidLocation() {
        RuleSet ruleSet = new RuleSet();
        ruleSet.add(NlpRule.CITY_NOT_NULL);
        ruleSet.add(NlpRule.STATE_NOT_NULL);
        ruleSet.add(NlpRule.COUNTRY_US);
        ruleSet.add(NlpRule.STATE_ABBREVIATED);
        return ruleSet;
    }
}

Main.java

public class Main {
    public static void main(String... args) {
        String location = "Santa Barbara,CA,USA";
        RuleSet ruleSet = RuleSets.isValidLocation();
        try {
            boolean isValid = (boolean) ruleSet.apply(location);
            System.out.println(isValid);
        } catch (Exception e) {
            e.getMessage();
        }
    }
}

设计2:使用抽象类

NlpRule.java

public abstract class NlpRule {
    public abstract Object apply(Object object);
    public final static NlpRule CITY_NOT_NULL = new NlpRule() {
        public Object apply(Object object) {
            String location = (String) object;
            String city = location.split(",")[0];
            if (city != null) {
                return true;
            }
            return false;
        }
    };
    public final static NlpRule STATE_NOT_NULL = new NlpRule() {
        public Object apply(Object object) {
            String location = (String) object;
            String city = location.split(",")[0];
            if (city != null) {
                return true;
            }
            return false;
        }
    };
    public final static NlpRule COUNTRY_US = new NlpRule() {
        public Object apply(Object object) {
            String location = (String) object;
            String country = location.split(",")[2];
            if (country.equals("US") || country.equals("USA")) {
                return true;
            }
            return false;
        }
    };
    public final static NlpRule STATE_ABBREVIATED = new NlpRule() {
        public Object apply(Object object) {
            String location = (String) object;
            String state = location.split(",")[1];
            if (state.length() == 2) {
                return true;
            }
            return false;
        }
    };
}

RuleSet.java

public class RuleSet {
    private List<NlpRule> rules;
    public RuleSet() {
        rules = new ArrayList<NlpRule>();
    }
    public RuleSet(List<NlpRule> rules) {
        this.rules = rules;
    }
    public void add(NlpRule rule) {
        rules.add(rule);
    }
    public boolean apply(Object object) throws Exception {
        boolean state = false;
        for (NlpRule rule : rules) {
            state = (boolean) rule.apply(object);
        }
        return state;
    }
}

RuleSets.java

import com.hgdata.design.one.NlpRule;
import com.hgdata.design.one.RuleSet;
public class RuleSets {
    private RuleSets() {
    }
    public static RuleSet isValidLocation() {
        RuleSet ruleSet = new RuleSet();
        ruleSet.add(NlpRule.CITY_NOT_NULL);
        ruleSet.add(NlpRule.STATE_NOT_NULL);
        ruleSet.add(NlpRule.COUNTRY_US);
        ruleSet.add(NlpRule.STATE_ABBREVIATED);
        return ruleSet;
    }
}

Main.java

public class Main {
    public static void main(String... args) {
        String location = "Santa Barbara,CA,USA";
        RuleSet ruleSet = RuleSets.isValidLocation();
        try {
            boolean isValid = (boolean) ruleSet.apply(location);
            System.out.println(isValid);
        } catch (Exception e) {
            e.getMessage();
        }
    }
}

更好的设计方法/模式?正如您所看到的,设计2去掉了接口和枚举。相反,它使用抽象类。我仍然想知道是否有更好的设计模式/方法来实现相同的。

使用初始化块实例化:

现在在上述两种设计的情况下。比如说,如果我需要实例化一个外部类,以便在我的apply逻辑中使用它,那么我就被迫使用初始化器块,我不完全知道这是否是一个好的做法。请参见下面的示例:

设计1:

...
STATE_ABBREVIATED(new Rule() {
        private CustomParser parser;
        {
            parser = new CustomParser();
        }
        @Override
        public Object apply(Object object) {
            String location = (String) object;
            location = parser.parse(location);
            String state = location.split(",")[1];
            if (state.length() == 2) {
                return true;
            }
            return false;
        }
    });
...
设计2:

...
public final static NlpRule STATE_ABBREVIATED = new NlpRule() {
        private CustomParser parser;
        {
            parser = new CustomParser();
        }
        public Object apply(Object object) {
            String location = (String) object;
            location = parser.parse(location);
            String state = location.split(",")[1];
            if (state.length() == 2) {
                return true;
            }
            return false;
        }
    };
...

Java专家请点灯!也请指出,如果你发现任何缺陷在上述两个设计。我需要知道每个设计的利弊,以帮助我做出正确的决定。我正在研究lambda,谓词和一些用户在评论中建议的其他几种模式。

这是一个有趣的问题,有很多可能的答案。在某种程度上,解决方案将取决于个人偏好。我经常遇到类似的问题,有以下建议。请注意,这些对我有用,但可能不适合您的需要。

  1. 使用enum。从长远来看,我觉得它们在错误检查和有用的容器(EnumSet等)方面比private static成员有很多优势,可以有效地使用它们。

  2. 使用接口而不是抽象类。在Java 8之前,使用抽象类有很多有用的理由。对于default成员来说,现在没有什么好的理由了(这只是我的观点——我相信其他人会不同意)。enum可以实现一个接口。

  3. 在Java 8中,与每个"规则"相关联的逻辑可以嵌入到lambda表达式中,从而使枚举的初始化代码更清晰。

  4. 保持lambdas非常短-最多只有一两个命令(最好是一个没有块的表达式)。这意味着将任何复杂的逻辑拆分为单独的方法。

  5. 使用单独的枚举对规则进行分类。没有什么好的理由把它们放在一起,通过将它们分开,可以使构造函数变得简单,因为它们的lambda表达式与它们的域完全相关。请看下面的例子来理解我的意思。

  6. 如果你有层次结构的规则,使用复合设计模式。它灵活而坚固。

所以把这些建议放在一起,我建议如下:

interface LocationRule{
    boolean isValid(Location location);
}
enum ValidValueRule implements LocationRule {
    STATE_NOT_NULL(location -> location.getState() != null),
    CITY_NOT_NULL(location -> location.getCity() != null);
    private final Predicate<Location> locationPredicate;
    ValidValueRule(Predicate<Location> locationPredicate) {
        this.locationPredicate = locationPredicate;
    }
    public boolean isValid(Location location) {
        return locationPredicate.test(location);
    }
}
enum StateSizeRule implements LocationRule {
    IS_BIG_STATE(size -> size > 1000000),
    IS_SMALL_STATE(size -> size < 1000);
    private final Predicate<Integer> sizePredicate;
    StateSize(Predicate<Integer> sizePredicate) {
        this.sizePredicate = sizePredicate;
    }
    public boolean isValid(Location location) {
        return sizePredicate.test(location.getState().getSize());
    }
}
class AllPassRule implements LocationRule {
    private final List<LocationRule > rules = new ArrayList<>();
    public void addRule(LocationRule rule) {
        rules.add(rule);
    }
    public boolean isValid(Location location) {
        return rules.stream().allMatch(rule -> rule.isValid(location));
    }
}
class AnyPassRule implements LocationRule {
    private final List<LocationRule > rules = new ArrayList<>();
    public void addRule(LocationRule rule) {
        rules.add(rule);
    }
    public boolean isValid(Location location) {
        return rules.stream().anyMatch(rule -> rule.isValid(location));
    }
}
class NegateRule implements LocationRule {
    private final Rule rule;
    public NegateRule(Rule rule) {
        this.rule = rule;
    }
    public boolean isValid(Location location) {
        return !rule.isValid(location);
    }
}

因此,例如,要实现位置必须在一个城市或一个不小的州的规则:

AnyPassRule cityOrNonSmallState = new AnyPassRule();
cityOrNonSmallState.addRule(ValidValueRule.CITY_NOT_NULL);
cityOrNonSmallState.addRule(new NegateRule(StateSize.IS_SMALL_STATE));
return cityOrNonSmallState.isValid(location);

已经有很多(开源的)Java规则引擎了-查看http://java-source.net/open-source/rule-engines &http://drools.org/

您可以从使用/检查其中之一的源代码开始(注意它不满足您的需求的地方),并从那里开始。

另一个可能的答案是使用DSL解析器来有效您的规则,在函数编程语言中,有一种叫做解析器组合器的东西,它可以从不同的基本解析器(规则)中构建更大的解析器(规则集)。这种方式的优点是灵活性,缺点是每次想要更改规则集时,都必须重新编码。

静态字段接口:

public interface NlpRule 
{
    Object apply(Object object);
    NlpRule CITY_NOT_NULL = object ->
    {
        String location = (String) object;
        String city = location.split(",")[0];
        return ...true/false;
    };
    // etc. 

有些人可能更喜欢方法而不是函数对象

public interface NlpRule 
{
    Object apply(Object object);
    static boolean cityNotNull(Object object) // java8: static method in interface
    {
        String location = (String) object;
        String city = location.split(",")[0];
        return ...true/false;
    };
    // etc. 
}
// use method reference as functional object
NlpRule rule = NlpRule::cityNotNull;
ruleset.add( NlpRule::cityNotNull );

或者你可以同时拥有方法和字段

public interface NlpRule 
{
    Object apply(Object object);
    NlpRule CITY_NOT_NULL = NlpRule::cityNotNull;
    static boolean cityNotNull(Object object)
    {
        ...
    };

示例规则都是String->boolean,不确定NlpRule为什么是Object->Object。如果规则确实可以接受/返回不同的类型,您可能应该泛化NlpRule<T,R>


CustomParser可以存储在包私有的helper类中

class NlpRuleHelper
{
    static final CustomParser parser = new CustomParser();
}
--
public interface NlpRule
...
    NlpRule STATE_ABBREVIATED = object -> 
    {
         ...
         location = NlpRuleHelper.parser.parse(location);

我认为你至少增加了一层不必要的代码。请记住,enum也可以实现接口,甚至可以有抽象方法。

/**
 * I don't like `Object` so I will adjust.
 */
public interface Rule {
    public boolean pass(String s);
}
/**
 * All pass country codes.
 */
public enum ValidCountry {
    US, USA;
    public static Set<String> all = new HashSet<>();
    static {
        for (ValidCountry c : ValidCountry.values()) {
            all.add(c.name());
        }
    }
}
public enum NlpRule implements Rule {
    CITY_NOT_NULL {
                @Override
                public boolean pass(String location) {
                    return location.split(",")[0] != null;
                }
            },
    STATE_NOT_NULL {
                @Override
                public boolean pass(String location) {
                    return location.split(",")[1] != null;
                }
            },
    COUNTRY_US {
                @Override
                public boolean pass(String location) {
                    return ValidCountry.all.contains(location.split(",")[2]);
                }
            },
    STATE_ABBREVIATED {
                @Override
                public boolean pass(String location) {
                    return location.split(",")[1].length() == 2;
                }
            };
    /**
     * You can even make Sets of them.
     */
    static Set<NlpRule> isValidLocation = EnumSet.of(CITY_NOT_NULL, STATE_NOT_NULL, COUNTRY_US, STATE_ABBREVIATED);
}
public void test() {
    String test = "Santa Barbara,CA,USA";
    for (Rule r : NlpRule.isValidLocation) {
        boolean pass = r.pass(test);
        System.out.println(r + "("" + test + "") - " + (pass ? "passes" : "FAILS"));
    }
}

相关内容

  • 没有找到相关文章

最新更新