设计模式(Java):多个类,同一字段集



考虑一个包括3个类的Java程序:EntryValidatorContextValidator具有一种方法boolean isValid(Entry entry, Context context),该方法将根据上下文确定条目对象的有效性。可以将验证器设置为以预设特定值(即MatchingMode.SPECIFIC)或从上下文(即MatchingMode.CONTEXT)的值检查相应字段的值,以检查每个entry.fieldMatchingModeValidator类中的嵌套枚举。考虑以下伪代码以进行进一步阐述:

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);
 }
}

现在您将拥有大量的规则实施,但是您的验证类将是干净且直接的。使用之前,您必须将正确的规则填充到验证类中。在规则中,您可以指定应检查哪个字段(自己的属性或上下文属性)。

最新更新