考虑一个包括3个类的Java程序:Entry
,Validator
和Context
。Validator
具有一种方法boolean isValid(Entry entry, Context context)
,该方法将根据上下文确定条目对象的有效性。可以将验证器设置为以预设特定值(即MatchingMode.SPECIFIC
)或从上下文(即MatchingMode.CONTEXT
)的值检查相应字段的值,以检查每个entry.field
。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个字段的维护开销不正确:
- 添加/删除3类中的字段
- 添加/删除fieldmm字段到验证器
- 复制/删除该字段的4线长验证
真的没有更好的方法进行这种验证吗?
一种方法是创建一个接管丑陋部分的辅助方法:
<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));
}
}
,您只在输入中只定义了一次实际字段,然后将验证器中的字段连接到其枚举。
首先一些想法:
-
在Java中,您无法像JavaScript那样对字段进行循环。这不是完全不可能的(您可以进行反思),但通常不是一个好选择。
-
如果您要构建生产代码,也许可以使用一些第三方工具(也许来自Apache或Spring)可以帮助您实现目标。由于您没有提及它们,所以我没有使用它们。
- 您应用程序的更大图景和流程可能会有所不同。
所以我的建议:
假设您有三个字段可以验证:
Type1 field1
Type2 field2
Type3 field3
我将创建一个验证器类:
由于注释而编辑(感谢荒谬的):
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;
}
在这种情况下,您可以通过可选的存在指示正确的验证方式。如果您正在寻找一种设计模式,我可以为验证器推荐一个建筑商。
解决方案两个:
如果您想保持清洁验证类,则可以使用以下模式:
创建一个接口:
interface Rule{
boolean check(Entry entry, Context context);
}
验证器类:
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);
}
}
现在您将拥有大量的规则实施,但是您的验证类将是干净且直接的。使用之前,您必须将正确的规则填充到验证类中。在规则中,您可以指定应检查哪个字段(自己的属性或上下文属性)。