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

时间:2017-09-16 12:29:19

标签: java design-patterns

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

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行长验证

真的没有更好的方法来进行这种验证吗?

2 个答案:

答案 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中的字段连接一次到它的枚举。