设计问题 - Java - 最佳实现方式是什么?

7

我有一个设计问题。

我有两个数据对象,它们分别是类A和类B的实例。A和B没有任何行为 - 它们是具有getter和setter的Java bean。我有一个Validation接口和10个实现它的不同验证的实现。我想在属性文件中指定哪个验证适用于哪个类。类似这样:

class A XYZValidation,ABCValidation

class B: ABCValidation, PPPValidation等

我该如何编写我的Validation类,以便为类A或类B的实例提供服务,或者其他任何我可能想要添加的类C?

interface Validation {
public boolean check(??);
}

> 我只想在这里加一句话,感谢所有回复这篇文章的人,并且我非常喜欢这个网站。Stackoverflow太棒了!


如果您关心的话,还有各种验证框架可供选择。请查看JSR 303的RI:https://www.hibernate.org/412.html - dfa
7个回答

8
你想过使用注解来标记你想在bean中验证的字段吗?
如果你有10个不同的验证,你可以指定10个注解。然后使用注解来标记这些字段:
@ValideStringIsCapitalCase
private String myString;

@ValidateIsNegative
private int myInt;

通过反射API遍历所有字段并查看它们是否被标记,例如:
public static <T> validateBean(T myBean) throws IllegalAccessException {
    Field[] fields = myBean.getClass().getDeclaredFields();
    // This does not take fields of superclass into account
    if (fields != null) {
        for (Field field : allFields) {
            if (field.isAnnotationPresent(ValideStringIsCapitalCase.class)) {
                field.setAccessible(true);
                Object value = field.get(existingEntity);
                // Validate
                field.setAccessible(false);
            }
        }
    }
}

一个选项是将整个类标记为您想要使用的验证器。
编辑:请记得包括注释。
@Retention(RetentionPolicy.RUNTIME)

为您的注释接口。

编辑2:请勿直接修改字段(如上面的示例)。而是使用反射访问它们的getter和setter。


这意味着给定类的验证器在编译时定义,但不一定是这种情况。同一个类可能需要在不同的用例中进行不同的验证。 - Huxi
没错,Jay必须决定这是否适合他/她的用例,或者他/她是否绝对需要一个属性文件。 - tputkonen

3
我可能误解了问题,但是这样的东西是否足够:
public class ValidationMappings {
    private Map<Class, Class<Validation>[]> mappings = new HashMap<Class, Class<Validation>[]>();

    public ValidationMappings() {
            mappings.put(A.class, new Class[]{XYZValidation.class, ABCValidation.class});
            mappings.put(B.class, new Class[]{ABCValidation.class, PPPValidation.class});
    }

    public Class[] getValidators(Class cls) {
            if (!mappings.containsKey(cls)) return new Class[]{};
            return mappings.get(cls);
    }
}

当您想获取特定类的验证器列表时,您需要调用getValidators(Class cls)方法并遍历每个验证器,并创建每个验证器实例并调用其检查方法。

3

可能是类似这样的东西吗?

interface Validation {
   public boolean check(Validatable x);
}

interface Validatable {
}


class A implements Validatable {
  ...
}

class Validator {
   public boolean validateObject(Validatable x){
      boolean validated = true;
      ... //read config file, check which validation classes to call
      //for each validation class v in the config file:
          if(!v.check(x)) validated = false;
      return validated;
   }
}

我也考虑过这个方向,但我更喜欢使用注解的解决方案。 - Artem Barger
我不喜欢这个,因为在你的检查方法中,你必须将Validatable强制转换为你的具体对象。泛型是更好、更安全的选择: class Validator<T> { boolean validate(T object); } - dfa
好的,我建议将泛型与我的解决方案相结合:<T extends Validatable> - Fortega
1
Validatable接口是一个人为的限制,它并没有给你带来任何好处。我有什么遗漏吗? - Huxi
它可以在编译时防止您尝试将无法验证的对象传递给validateObject()方法。 - Fortega

2

您是不是想说:

public interface Validation<T> {
    boolean check(T object)
}

这样的接口不允许鸭子类型的验证,即只需要某个具有特定返回值的方法存在。相反,它要求该方法在接口中被定义。这是一种不必要的限制。 - Huxi

2
如果你只想让它处理任何对象,那么你的接口将是 Object 的。 public boolean check(Object o);
除非你想使用一些标记接口来标记适合验证的类。

0

首先,我会使用以下接口

interface Validator {
    boolean isValid(Object object);
}

隐式记录返回值实际意义的方式。

其次,我建议在接口中记录如果验证器不知道如何处理给定实例时期望的行为。

interface Validator {
    /**
     * @return false if this validator detects that the given instance is invalid, true if the given object is valid or this Validator can't validate it.
     */
    boolean isValid(Object object);
}

那样的话,你只需要一个 Validators 列表,可以将你的对象传递给它们。
如果实现正确,不兼容的 Validator 的性能影响应该可以忽略不计,例如使用早期 instanceof。
另外,我会使用 Validators 列表而不是集合,这样可以按复杂度对它们进行排序。将廉价(在性能方面)的 Validators 作为列表的开头,作为优化。
然后,你可以使用通用的验证代码,例如:
public class Validators {
    public static boolean isValid(Object o, Collection<Validator> validators) {
        for(Validator current : validators) {
            if(!current.isValid()) return false;
        }
        return true;
    }
}

根据您的用例,返回界面中不同于布尔值的东西可能是一个好主意。如果您需要有关错误信息(例如要显示它)的信息,您需要返回该信息。
在这种情况下,最好保持上述循环运行,以便获得所有验证错误,而不仅仅是第一个。

0

访问者模式可以解决这个问题

调用访问者验证器,可以实现以下功能:

公共接口Validatable: public interface Validatable { public boolean validate(Validator v); }
公共接口Validator: public interface Validator { public boolean validate(A a); public boolean validate(B b); }
类A实现Validatable接口: public class A implements Validatable {
public boolean validate(Validator v){ return v.validate(this); }
}
类B实现Validatable接口: public class B implements Validatable {
public void validate(Validator v) { return v.validate(this); }
}
默认验证器GenericValidator无法验证A和B: public class GenericValidator implements Validator {
public boolean validate(A a) { throw new UnsupportedOperationException("Cannot validate A"); }
public boolean validate(B b) { throw new UnsupportedOperationException("Cannot validate B"); } }
由于XYZValidation只能在A上运行,因此它仅覆盖A验证: public class XYZValidation extends GenericValidator { public boolean validate(A a) { // 验证a return isVAlid(a); } }
由于ABCValidation应该在A和B上运行,因此它覆盖了A和B的验证: public class ABCValidation extends GenericValidator { public boolean validate(A a) { // 验证a return isVAlid(a); }
public boolean validate(B b) { // 验证b return isVAlid(b); } }
由于PPPValidation只能在B上运行,因此它仅覆盖B验证: public class PPPValidation extends GenericValidator { public boolean validate(B b) { // 验证b return isVAlid(b); } }

1
@Reginaldo 请考虑一下:如果我添加了C类,你的解决方案会发生什么? - Jay
请随意称呼我为亵渎者,但我认为在Java环境中原始的访问者模式http://en.wikipedia.org/wiki/File:VisitorClassDiagram.svg是反模式。它包含被访问类和访问者接口之间的相互依赖,在支持运行时类型信息(RTTI)的语言(如Java)中根本不必要。访问者需要知道它想要访问的类,但反过来则不需要。此外,访问者接口可以轻松地在“Object”上工作,并仅在可以处理它时才处理它,例如通过返回if (!(o instanceof Whatever))。 - Huxi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接