Перевірка даних: розділений клас чи ні?


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?
    }
}

Відповіді:


7

Добре делегувати будь-яку логіку за допомогою композиції, якщо ця логіка буде динамічно змінюватися під час виконання програми. Складні перевірки, як ті, які ви пояснюєте, є настільки ж хорошим кандидатом, як і будь-який, який буде делегований до іншого класу за допомогою складу.

Пам’ятайте, хоча перевірки можуть відбуватися в різні моменти.

Ігнорування конкретного валідатора, як у вашому прикладі, є поганою ідеєю, оскільки він приєднує ваш клас подій до конкретного валідатора.

Припустимо, ви не використовуєте жодної рамки DI.

Ви можете додати валідатор до конструктора або ввести його методом setter. Я пропоную метод творця на заводі інстанціювати як Подія, так і валідатор, а потім передає його або конструктору подій, або методом setValidator.

Очевидно, слід записати інтерфейс Validator або абстрактний клас, щоб ваші класи залежали від нього, а не від будь-якого конкретного валідатора.

Виконання методу валідації в конструкторі може бути проблематичним, оскільки все стан, який ви хочете перевірити, можливо, ще не існує.

Я пропоную вам створити вірний інтерфейс і дозволити своїм класам реалізувати його, щоб цей інтерфейс міг мати метод validate ().

Таким чином верхні компоненти вашої програми викликають метод валідації за бажанням (який, в свою чергу, делегований члену валідатора).

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

==> Подія.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;
}

==> Тест.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

Моя відповідь - це нормально створити ще один клас для складної перевірки. Я лише додав кілька порад для вас, щоб уникнути жорсткого зчеплення та отримати більшу гнучкість.
Тулан Кордова

Якщо я мав би додати повідомлення про помилки до списку чи диктатури, чи повинні вони бути Validatorчи входити Validable? І як я можу працювати з цими повідомленнями спільно з ValidationException?
dabadaba

1
@dabadaba У цьому списку має бути член виконавців IValidator, але IValidable повинен додати доступ до цього методу, щоб його делегували виконавці. Я припускав, що можна повернути більше одного повідомлення про перевірку. Клацніть на відредаговану п’ять хвилин тому посилання внизу, щоб побачити відмінності. Краще, якщо ви будете використовувати вид збоку (без відмітки).
Тулен Кордова

3
Можливо, педантичний, але я припускаю Validatable, що це набагато краще ім'я, ніж Validableсправжній прикметник
user919426

4

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

У цьому проблема. В ідеалі ви повинні запобігти недійсному стану ваших об'єктів: не допускайте інстанцій з недійсним станом, і якщо у вас повинні бути налаштування та інші методи, що змінюють стан, киньте тут виняток.

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

Це не суперечливо. Якщо логіка перевірки є досить складною для гарантування власного класу, ви все одно можете делегувати перевірку. І якщо я зрозумів, що ви правильно, це саме ви робите зараз:

Тоді, перш ніж ідентифікувати достовірний об'єкт, я проведу перевірку, щоб запобігти незаконним значенням.

Якщо у вас все ще є налаштування, які можуть призвести до недійсного стану, переконайтесь, що перевірити його, перш ніж змінити стан. Інакше ви отримаєте повідомлення про помилку, але все-таки залишите об’єкт недійсним. Знову ж таки: не дозволяйте вашим об'єктам мати недійсний стан

Крім того, чи варто використовувати булеві методи повернення перевірки? Або просто кинути виняток, якщо перевірка не вдалася? Мені здається, найкращим варіантом було б булеві методи повернення і викинути виняток, коли об'єкт миттєвий

Для мене звучить добре, оскільки валідатор розраховує на дійсне або недійсне введення, тому з точки зору, недійсне введення не є винятком.

Оновлення: Якщо "система в цілому" стає недійсною, причина полягає в тому, що якийсь об'єкт повинен бути змінений (наприклад, програвач), а якийсь можливий об'єкт вищого рівня (наприклад, подія), на який посилається цей об'єкт, є умовою, яка більше не виконується. . Тепер рішенням буде не допускати прямих змін до гравця, які могли б зробити щось недійсне на більш високому рівні. Тут допомагають незмінні предмети . Тоді змінений гравець - інший об’єкт. Після того, як гравець асоціюється з подією, ви можете змінити його лише від самої події (адже вам доведеться змінити посилання на новий об'єкт) і знову негайно перевірити.


А як щодо того, щоб не проводити жодних перевірок у інстанції та setters та мати перевірку як операцію в сторону? Щось на зразок Тулана Кордова. Оскільки перевірка та викидання виключень у сеттер або конструкторі може працювати для цього члена чи об'єкта, але це не допомагає перевірити, чи нормально система в цілому.
dabadaba

@dabadaba моя відповідь полягала в тому, щоб довго коментувати, дивіться оновлення вище
Fabian Schmengler

Я не хочу використовувати незмінні об'єкти, але хочу змінити свої класи. І я не знаю, як зробити незмінні об’єкти, якщо ви не маєте на увазі остаточне ключове слово. У такому випадку мені доведеться сильно змінити свій дизайн, оскільки я частково будую події та турніри з додатковими налаштуваннями (оскільки ці дані є додатковими та налаштовуються).
dabadaba

3
Щоб створити незмінні об’єкти, видаліть усі задачі та використовуйте лише конструктори для встановлення значень. Якщо у вас є необов'язкові дані або дані, які недоступні одразу, подумайте про використання шаблону для розробників
Fabian Schmengler
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.