考虑一个包含3个类的Java程序:Entry
,Validator
和Context
。 Validator
有一个方法boolean isValid(Entry entry, Context context)
,它将根据上下文确定条目对象的有效性。可以将验证器设置为针对预设的特定值(即entry.field
)或针对来自上下文的对应字段的值(即MatchingMode.SPECIFIC
)检查每个MatchingMode.CONTEXT
。 MatchingMode
是Validator
类中的嵌套枚举。请考虑以下伪代码以进一步阐述:
Validator::boolean isValid(Entry entry, Context context)
{
boolean valid = true;
for(each field):
if(this.field.matchingMode == MatchingMode.SPECIFIC)
valid &= (entry.field.equals(this.field));
else if(this.field.matchingMode == MatchingMode.CONTEXT)
valid &= (entry.field.euqals(context.field));
return valid;
}
备注:
鉴于上述验证用例,您如何建议以干燥和类型安全的方式实现此代码?
修改1:
验证每个字段的逻辑是相同的。看看当前的实时代码(简化为有意义的例子):
class Validator
{
int field1;
MatchingMode field1MM;
String field2;
MatchingMode field2MM;
// and a few more filed/MMs
boolean isValid(Entry entry, Context context)
{
boolean valid = true;
if(this.field1MM == MatchingMode.SPECIFIC)
valid &= (entry.field1.equals(this.field1));
else if(this.field1MM == MatchingMode.CONTEXT)
valid &= (entry.field1.euqals(context.field1));
if(this.field2MM == MatchingMode.SPECIFIC)
valid &= (entry.field2.equals(this.field2));
else if(this.field2MM == MatchingMode.CONTEXT)
valid &= (entry.field2.euqals(context.field2));
// copy and paste for each field/MM
return valid;
}
}
仅添加/删除1字段的维护开销感觉不对:
真的没有更好的方法来进行这种验证吗?
答案 0 :(得分:1)
首先想一想:
在Java中,您无法像在JavaScript中那样对字段进行循环。这并非完全不可能(你可以玩反射),但通常不是一个好的选择。
如果您正在构建生产代码,也许您可以使用一些第三方工具(可能来自Apache或Spring),它们可以帮助您实现目标。由于你没有提到它们,我没有使用它们。
所以我的建议是:
假设您有三个要验证的字段:
class Validator{
public final Optional<Type1> optionalField1;
public final Optional<Type2> optionalField2;
public final Optional<Type3> optionalField3;
public Validator(Optional<Type1> optionalField1,
Optional<Type2> optionalField2,
Optional<Type3> optionalField3){
this.optionalField1 = optionalField1;
this.optionalField2 = optionalField2;
this.optionalField3 = optionalField3;
}
public boolean isValid(Entry entry, Context context){
boolean answer = true;
answer &= entry.field1.equals(optionalField1.orElse(context.field1));
answer &= entry.field2.equals(optionalField2.orElse(context.field2));
answer &= entry.field3.equals(optionalField3.orElse(context.field3));
return answer;
}
我会创建一个Validator类:
由于评论而编辑(感谢荒谬的心灵):
interface Rule{
boolean check(Entry entry, Context context);
}
在这种情况下,您可以通过Optional选项指示正确的验证方式。如果您正在寻找设计模式,我可以为验证器推荐构建器。
解决方案二:
如果您想保持清洁验证类,可以使用以下模式:
创建一个界面:
class Validator{
List<Rule> rules = new ArrayList<>();
public boolean validate(Entry entry, Context context){
boolean answer = true;
for(Rule rule : rules){
answer &= rule.check(entry,context);
}
return answer;
}
public void addRule(Rule rule){
rules.add(rule);
}
}
验证员类:
mysite.com/membership.php?c=h7Y6734da
现在您将拥有大量的Rule实现,但您的验证类将是干净和直接的。在使用之前,您必须将正确的规则填充到验证类。在规则中,您可以指定应根据哪个值(自己的属性或上下文属性)检查哪个字段。
答案 1 :(得分:1)
一种方法是创建一个辅助方法来接管丑陋的部分:
<T> boolean contextualEquals(T entryValue, T validatorValue, T contextValue, MatchinMode mode) {
if (mode == SPECIFIC) {
return Objects.equals(entryValue, validatorValue);
}
return Objects.equals(entryValue, contextValue);
}
之后,验证器方法内的调用变得更加清晰。
boolean isValid(Entry entry, Context context) {
boolean valid = true;
valid &= contextualEquals(entry.fieldA, this.fieldA, context.fieldA, fieldA.matchingMode);
valid &= contextualEquals(entry.fieldB, this.fieldB, context.fieldB, fieldB.matchingMode);
return valid;
}
编辑1: 您可以使用字段枚举扩展此答案:
enum Fields {
FIELD_A, FIELD_B;
}
您的上下文或多或少是地图:
class Validator {
// if you stick with context, this should be a context then...
Map<Fields, Object> matchingMode = new HashMap<>();
static {
matchingMode.put(Fields.FIELD_A, 123);
}
boolean isValid(Entry entry, Map<Fields, Object> context) {
boolean valid = true;
valid &= contextualEquals(entry.fieldA, Fields.FIELD_A, context, matchingMode);
valid &= contextualEquals(entry.fieldB, Fields.FIELD_B, context, matchingMode);
return valid;
}
<T> boolean contextualEquals(T entryValue, Fields field, Map<> context, Map<> matchingMode) {
if (matchingMode.containsKey(field)) {
return Objects.equals(entryValue, matchingMode.get(field));
}
return Objects.equals(entryValue, context.get(field));
}
}
你可以在Entry中只定义一次真实字段,并将Validator中的字段连接一次到它的枚举。