Проверка данных: отдельный класс или нет?

16

Когда у меня есть много данных, которые необходимо проверить, я должен создать новый класс с единственной целью проверки или я должен придерживаться проверки в методе?

Мой конкретный пример рассматривает турнир и класс события / категории: Tournamentи Event, который моделирует спортивный турнир, и у каждого турнира есть одна или несколько категорий.

В этих классах есть все, что нужно проверить: игроки должны быть пустыми, должны быть уникальными, количество матчей, которые должен сыграть каждый игрок, количество игроков в каждом матче, предопределенные матчи и действительно большой набор вещей, включающий в себя гораздо больше сложные правила.

Есть также некоторые части, которые мне нужно проверить в целом, например, как классы интегрируются друг с другом. Например, унитарная проверка Playerможет быть вполне приемлемой, но если в событии дважды участвует один и тот же игрок, это ошибка проверки.

Так как насчет этого ?: Я забыл об абсолютно любой предварительной проверке при использовании сеттеров классов моей модели и подобных методов для добавления данных, и вместо этого я позволил классам проверки обрабатывать это.

Таким образом , мы будем иметь что - то вроде EventValidatorс Eventчленом, и 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?
    }
}
dabadaba
источник

Ответы:

7

Можно делегировать любую логику с помощью композиции, если эта логика будет динамически изменяться во время выполнения программы. Сложные проверки, подобные тем, которые вы объясняете, являются таким же хорошим кандидатом, как и любые другие, которые можно делегировать другому классу посредством композиции.

Помните, что проверки могут происходить в разные моменты.

Создание конкретного валидатора, как в вашем примере, является плохой идеей, потому что он связывает ваш класс Event с этим конкретным валидатором.

Давайте предположим, что вы не используете DI-фреймворк.

Вы можете добавить валидатор в конструктор или добавить его с помощью метода установки. Я предлагаю метод-создатель на фабрике, который создает экземпляр Event и валидатора, а затем передает его либо в конструктор событий, либо с помощью метода 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);
            }
        }
    }
}
Тулаинс Кордова
источник
Так что с точки зрения иерархии классов это мало чем отличается от того, что я предложил, не так ли? Вместо того, чтобы создавать экземпляр и вызывать валидатор внутри конструктора, он будет создаваться и вызываться при необходимости в потоке выполнения, это основное различие, которое я вижу.
Дабадаба
Мой ответ в основном заключается в том, что можно создать еще один класс для сложной проверки. Я просто добавил несколько советов для вас, чтобы избежать жесткой связи и получить больше гибкости.
Тулаинс Кордова
Если бы я должен был добавить сообщения об ошибках в список или словарь, он должен быть в Validatorили в Validable? И как я могу работать с этими сообщениями в сочетании с ValidationException?
Дабадаба
1
@dabadaba Этот список должен быть членом разработчиков IValidator, но IValidable должен добавить доступ к этому методу, чтобы разработчики делегировали его. Я предположил, что может быть возвращено более одного сообщения проверки. Нажмите на отредактированную ссылку n минут назад внизу, чтобы увидеть различия. Лучше, если вы используете вид рядом (без уценки).
Тулаинс Кордова
3
Возможно, он педантичен, но я предполагаю, Validatableчто это имя намного лучше, чем Validableнастоящее прилагательное
user919426
4

Я забыл про абсолютно любую предварительную проверку при использовании сеттеров классов моей модели и подобных методов для добавления данных

Это проблема. В идеале вы должны предотвратить недопустимое состояние ваших объектов: не допускайте создания экземпляров с недопустимым состоянием, и если у вас должны быть сеттеры и другие методы изменения состояния, выведите исключение прямо здесь.

вместо этого я позволяю валидационным классам обрабатывать валидацию.

Это не противоречиво. Если логика проверки достаточно сложна, чтобы гарантировать собственный класс, вы все равно можете делегировать проверку. И если я вас правильно понял, это именно то, что вы делаете сейчас:

Затем, прежде чем создавать экземпляр допустимого объекта, я выполню проверку, чтобы предотвратить недопустимые значения.

Если у вас все еще есть сеттеры, которые могут привести к недопустимому состоянию, убедитесь, что вы проверили его перед фактическим изменением состояния. В противном случае вы получите сообщение об ошибке, но все равно оставите свой объект с недопустимым состоянием. Опять же: предотвратите недопустимое состояние ваших объектов

Кроме того, я должен использовать методы проверки логического возврата? Или просто выбросить исключение, если проверка не удалась? Мне кажется, что лучшим вариантом будет логическое возвращение методов и выдача исключения, когда объект создается

Звучит хорошо для меня, так как валидатор ожидает допустимый или недействительный ввод, поэтому с его точки зрения недопустимый ввод не является исключением.

Обновление: если «система в целом» становится недействительной, причина в том, что какой-то объект должен был измениться (например, игрок), а некоторые объекты более высокого уровня (например, событие), которые ссылаются на этот объект, имеют условие, которое больше не выполняется , Теперь решение состоит в том, чтобы не допустить прямых изменений в игроке, которые могут сделать что-то недействительным на более высоком уровне. Здесь помогают неизменные объекты . Тогда измененный игрок - это другой объект. Как только игрок связан с событием, вы можете изменить его только из самого события (потому что вам придется изменить ссылку на новый объект) и снова подтвердить его немедленно.

Фабиан Шменглер
источник
Как насчет отсутствия каких-либо проверок в экземплярах и установщиках и проверки в качестве отдельной операции? Нечто подобное предложил Тулаинс Кордова. Потому что проверка и создание исключения в установщике или конструкторе может работать для этого члена или объекта, но это не помогает проверить, в порядке ли система в целом.
Дабадаба
@dabadaba мой ответ был длинным для комментария, пожалуйста, смотрите обновление выше
Фабиан Шменглер
Я не хочу использовать неизменяемые объекты, я хочу иметь возможность изменять свои классы. И я не знаю, как создавать неизменяемые объекты, если вы не ссылаетесь на последнее ключевое слово. В этом случае мне пришлось бы сильно изменить свой дизайн, потому что я частично строю события и турниры с дополнительными сеттерами (потому что эти данные являются дополнительными и настраиваемыми).
Дабадаба
3
Чтобы сделать неизменяемые объекты, удалите все сеттеры и используйте только конструкторы для установки значений. Если у вас есть необязательные данные или данные, которые не доступны сразу, рассмотрите возможность использования шаблона построителя: developer.amd.com/community/blog/2009/02/06/…
Фабиан Шменглер,