我正在用 Java 实现一个规则引擎。我的规则引擎预定义了独立规则和规则集的列表。这里的规则只是一个逻辑。而一个规则集将这些简单的规则组合成一个有序的集合。
我是一个不错的 Java 开发人员,但不是 Guru。为此,我的同事向我推荐了两种设计。我对这两种设计都不满意,因此提出了这个问题。
我的项目中的规则示例: 假设输入是美国的位置,例如,美国加利福尼亚州圣巴巴拉或美国俄亥俄州,这通常是具有城市、州和国家字段的一些明确定义的格式。然后我可以有一些规则如下:
规则 1: 城市不为空
规则 2: 状态不为空
规则 3: 国家等于美国或美国
规则 4: 状态长度等于 2
我的项目中的规则集示例:
规则集: 有效位置 此规则集是上述定义的规则的有序集。
我实现的两个设计模板如下:
设计 1:将枚举与匿名内部类一起使用
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 摆脱了接口(interface)和枚举。它改为使用抽象类。我仍然想知道是否有更好的设计模式/方法来实现它。
使用初始化 block 进行实例化:
现在在上述两种设计的情况下。比如说,如果我需要实例化一个外部类以在我的应用逻辑中使用它,那么我将被迫使用我不完全知道是否是一个好习惯的初始化程序 block 。请参阅以下此类场景的示例:
设计 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、谓词和其他几种模式。
最佳答案
这是一个有趣的问题,有很多可能的答案。在某种程度上,解决方案将取决于个人喜好。我经常遇到类似的问题,有以下建议。请注意,这些对我有用,但可能不适合您的需求。
使用 枚举。从长远来看,我觉得它们在错误检查和可以有效使用它们的有用容器(EnumSet 等)方面比 private static 成员有很多优势。
在抽象类之上使用接口(interface)。在 Java 8 之前,有一些有用的理由使用抽象类。对于 default 成员,现在没有充分的理由(只是我的看法 - 我相信其他人会不同意)。一个枚举可以实现一个接口(interface)。
在 Java 8 中,与每个“规则”相关的逻辑都可以嵌入到 lambda 表达式中,这使得枚举的初始化代码更加清晰。
保持 lambdas 非常短——最多只有一两个命令(最好是一个没有 block 的表达式)。这意味着将任何复杂的逻辑拆分为单独的方法。
使用单独的枚举对规则进行分类。没有充分的理由将它们全部合二为一,通过将它们拆分出来,您可以通过准确地使用与其域相关的 lambda 表达式来简化构造函数。请参阅下面的示例以了解我的意思。
如果您有层次结构的规则,请使用复合设计模式。它既灵活又健壮。
因此,将这些建议放在一起,我会提出如下建议:
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 - 在 Java 中设计规则引擎的有效设计模式/风格是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30430818/
类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc
我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co
我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%
我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i
为什么4.1%2返回0.0999999999999996?但是4.2%2==0.2。 最佳答案 参见此处:WhatEveryProgrammerShouldKnowAboutFloating-PointArithmetic实数是无限的。计算机使用的位数有限(今天是32位、64位)。因此计算机进行的浮点运算不能代表所有的实数。0.1是这些数字之一。请注意,这不是与Ruby相关的问题,而是与所有编程语言相关的问题,因为它来自计算机表示实数的方式。 关于ruby-为什么4.1%2使用Ruby返
鉴于我有以下迁移:Sequel.migrationdoupdoalter_table:usersdoadd_column:is_admin,:default=>falseend#SequelrunsaDESCRIBEtablestatement,whenthemodelisloaded.#Atthispoint,itdoesnotknowthatusershaveais_adminflag.#Soitfails.@user=User.find(:email=>"admin@fancy-startup.example")@user.is_admin=true@user.save!ende
它不等于主线程的binding,这个toplevel作用域是什么?此作用域与主线程中的binding有何不同?>ruby-e'putsTOPLEVEL_BINDING===binding'false 最佳答案 事实是,TOPLEVEL_BINDING始终引用Binding的预定义全局实例,而Kernel#binding创建的新实例>Binding每次封装当前执行上下文。在顶层,它们都包含相同的绑定(bind),但它们不是同一个对象,您无法使用==或===测试它们的绑定(bind)相等性。putsTOPLEVEL_BINDINGput
我想在一个没有Sass引擎的类中使用Sass颜色函数。我已经在项目中使用了sassgem,所以我认为搭载会像以下一样简单:classRectangleincludeSass::Script::FunctionsdefcolorSass::Script::Color.new([0x82,0x39,0x06])enddefrender#hamlengineexecutedwithcontextofself#sothatwithintemlateicouldcall#%stop{offset:'0%',stop:{color:lighten(color)}}endend更新:参见上面的#re
我可以得到Infinity和NaNn=9.0/0#=>Infinityn.class#=>Floatm=0/0.0#=>NaNm.class#=>Float但是当我想直接访问Infinity或NaN时:Infinity#=>uninitializedconstantInfinity(NameError)NaN#=>uninitializedconstantNaN(NameError)什么是Infinity和NaN?它们是对象、关键字还是其他东西? 最佳答案 您看到打印为Infinity和NaN的只是Float类的两个特殊实例的字符串
如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象