数据验证:是否是分隔类?


16

当我有大量需要验证的数据时,我应该仅出于验证目的创建一个新类,还是应该坚持方法内验证?

我的特定示例设想了一个锦标赛和一个事件/类别类:TournamentEvent,它模拟了一个体育锦标赛,每个锦标赛都有一个或多个类别。

这些类别中有很多事情需要验证:球员应该为空,应该是唯一的,每个球员应该参加的比赛数量,每次比赛都有的球员数量,预定义的对决,以及包括许多其他东西在内的非常重要的事情。复杂的规则。

我还需要整体验证某些部分,例如类如何相互集成。例如,对a Player进行单一验证就可以了,但是如果一个事件两次具有相同的玩家,那就是验证错误。

那怎么办呢::使用模型类的设置器和类似方法添加数据时,我完全忘记了任何预检查,而是让验证类来处理。

因此,我们将有类似EventValidatorEvent作为成员,和validate()用来验证整个对象的方法,再加上单一的方法来验证所有成员的规则。

然后,在实例化有效对象之前,我将执行验证以防止出现非法值。

我的设计正确吗?我应该做些不同的事情吗?

另外,我应该使用布尔值返回验证方法吗?或者,如果验证失败,则抛出异常?在我看来,最好的选择是布尔返回方法,并在实例化对象时引发异常,例如:

public Event() {
    EventValidator eventValidator = new EventValidator(this);
    if (!eventValidator.validate()) {
        // show error messages with methods defined in the validator
        throw new Exception(); // what type of exception would be best? should I create custom ones?
    }
}

Answers:


7

可以通过组合来委派任何逻辑如果逻辑要在程序执行期间动态变化,则可以。像您解释的那样,复杂的验证与通过组合委派给另一个类的验证一样,都是很好的候选人。

请记住,尽管验证可以在不同的时刻进行。

像您的示例中那样实例化一个具体的验证器是一个坏主意,因为它将Event类耦合到该特定的验证器。

假设您没有使用任何DI框架。

您可以将验证器添加到构造函数中,或使用setter方法将其注入。我建议工厂中的创建者方法将Event和Validator实例化,然后将其传递给事件构造器或setValidator方法。

显然,应该编写Validator接口和/或抽象类,以便您的类依赖于它,而不依赖于任何具体的验证器。

在构造函数中执行validate方法可能会出现问题,因为您要验证的所有状态可能尚未就绪。

我建议您创建一个Validable接口并让您的类实现它,该接口可以具有validate()方法。

这样,您的应用程序的上层组件便会随意调用validate方法(然后将其委托给Validator成员)。

==> IValidable.java <==

import java.util.List;

public interface IValidable {
    public void setValidator(IValidator<Event> validator_);
    public void validate() throws ValidationException;
    public List<String> getMessages();
}

==> IValidator.java <==

import java.util.List;

public interface IValidator<T> {
    public boolean validate(T e);
    public List<String> getValidationMessages();
}

==> Event.java <==

import java.util.List;

public class Event implements IValidable {

    private IValidator<Event> validator;

    @Override
    public void setValidator(IValidator<Event> validator_) {
        this.validator = validator_;
    }

    @Override
    public void validate() throws ValidationException {
        if (!this.validator.validate(this)){
            throw new ValidationException("WTF!");
        }
    }

    @Override
    public List<String> getMessages() {
        return this.validator.getValidationMessages();
    }

}

==> SimpleEventValidator.java <==

import java.util.ArrayList;
import java.util.List;

public class SimpleEventValidator implements IValidator<Event> {

    private List<String> messages = new ArrayList<String>();
    @Override
    public boolean validate(Event e) {
        // do validations here, by accessing the public getters of e
        // propulate list of messages is necessary
        // this example always returns false    
        return false;
    }

    @Override
    public List<String> getValidationMessages() {
        return this.messages;
    }

}

==> ValidationException.java <==

public class ValidationException extends Exception {
    public ValidationException(String message) {
        super(message);
    }

    private static final long serialVersionUID = 1L;
}

==> Test.java <==

public class Test {
    public static void main (String args[]){
        Event e = new Event();
        IValidator<Event> v = new SimpleEventValidator();
        e.setValidator(v);
        // set other thins to e like
        // e.setPlayers(player1,player2,player3)
        // e.setNumberOfMatches(3);
        // etc
        try {
            e.validate();
        } catch (ValidationException e1) {
            System.out.println("Your event doesn't comply with the federation regulations for the following reasons: ");
            for (String s: e.getMessages()){
                System.out.println(s);
            }
        }
    }
}

因此,就类层次结构而言,这与我的建议没有太大不同,是吗?无需在构造函数内部实例化和调用验证器,而是在执行流中需要时创建并调用它,这是我可以看到的主要区别。
dabadaba '16

我的回答基本上是,可以创建另一个类进行复杂的验证。我刚刚为您添加了一些建议,以避免硬耦合并获得更大的灵活性。
TulainsCórdova'16

如果要在列表或词典中添加错误消息,它应该在Validator还是在Validable?以及如何将这些消息与结合使用ValidationException
dabadaba '16

1
@dabadaba该列表应该是IValidator实现者的成员,但是IValidable应该添加对该方法的访问权限,以便实现者委托它。我假设可以返回多个验证消息。单击底部n分钟前已编辑的链接,以便您可以看到不同之处。最好使用并排视图(无减价)。
图兰斯·科尔多瓦

3
可能是书呆子,但我想这Validatable是一个比Validable真正的形容词好得多的名称
user919426 2016年

4

使用模型类的设置器和类似方法添加数据时,我几乎完全无需进行任何预检查

那就是问题所在。理想情况下,应防止对象具有无效状态:不允许实例化为无效状态,并且如果必须具有setter和其他状态更改方法,请在该位置抛出异常。

相反,我让验证类处理验证。

这并不矛盾。如果验证逻辑足够复杂以保证其具有自己的类,则仍可以委托验证。如果我了解您的正确,这正是您现在正在做的事情:

然后,在实例化有效对象之前,我将执行验证以防止出现非法值。

如果您还有可能会导致无效状态的设置器,请确保实际更改状态之前进行验证。否则,您将收到一条错误消息,但仍然使对象处于无效状态。再次:防止对象具有无效状态

另外,我应该使用布尔值返回验证方法吗?或者,如果验证失败,则抛出异常?在我看来,最好的选择是布尔返回方法,并在实例化对象时引发异常

对我来说听起来不错,因为验证器期望输入有效或无效,因此从其角度来看,无效输入也不例外。

更新:如果“整个系统”变得无效,则原因是某些对象必须已更改(例如,播放器),而引用该对象的某些可能更高级别的对象(例如,事件)的条件已不再满足。现在的解决方案是不允许对播放器进行直接更改,以免在更高级别上使某些内容无效。这是不可变对象的帮助。然后,更改的玩家是另一个对象。玩家与事件相关联后,您只能从事件本身进行更改(因为您必须将引用更改为新对象),然后再次进行验证。


在实例化和设置器中不进行任何检查并将验证作为备用操作该怎么办?像图兰斯·科尔多瓦(TulainsCórdova)所说的东西。因为在setter或构造函数中验证并引发异常可能对该成员或对象起作用,但这不利于检查系统整体是否正常。
dabadaba '16

@dabadaba我的回答是想发表评论了,请参阅上面的更新
Fabian Schmengler

我不想使用不可变的对象,但希望能够修改我的类。而且我不知道如何制作不可变的对象,除非您指的是final关键字。在那种情况下,我将不得不进行很多更改,因为我会使用其他设置工具来部分构造赛事和锦标赛(因为此数据是可调整的且可配置的)。
dabadaba '16

3
要创建不可变的对象,请删除所有设置器,仅使用构造函数设置值。如果您有可选的数据或不提供一次数据,可以考虑使用建造者模式:developer.amd.com/community/blog/2009/02/06/...
法比安Schmengler
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.