Validazione dati: classe separata o no?

12

Quando ho molti dati che devono essere convalidati, dovrei creare una nuova classe per il solo scopo di validazione o dovrei attenermi alla convalida in-method?

Il mio esempio particolare contempla un torneo e una classe evento / categoria: Tournament e Event , che modella un torneo di sport e ogni torneo ha una o più categorie.

Ci sono tutti i tipi di cose da convalidare in queste classi: i giocatori dovrebbero essere vuoti, dovrebbero essere unici, il numero di partite che ogni giocatore dovrebbe giocare, il numero di giocatori che ogni partita ha, i matchup predefiniti e un grande eccetera incluse regole molto più complesse.

Ci sono anche alcune parti che ho bisogno di convalidare nel suo complesso, come il modo in cui le classi si integrano l'una con l'altra. Ad esempio, la convalida unitaria di Player può essere perfetta, ma se un evento ha lo stesso giocatore due volte, si tratta di un errore di convalida.

Che ne dici ?: Mi dimentico assolutamente di qualsiasi pre-controllo quando uso i setter delle mie classi di modelli e metodi simili per aggiungere dati e invece permetto alle classi di validazione di gestirli.

Quindi avremo qualcosa come EventValidator con un Event come membro e un metodo validate() che convalida l'intero oggetto, oltre a metodi singolari per convalidare tutte le regole dei membri.

Quindi, prima di creare un'istanza di un oggetto valido, eseguirò la convalida per evitare valori illegali.

Il mio progetto è corretto? Dovrei fare qualcosa di diverso?

Inoltre, dovrei usare i metodi di validazione di restituzione booleana? Oppure lanciare un'eccezione se la convalida fallisce? A mio parere, l'opzione migliore sarebbe rappresentata dai metodi di restituzione booleani e dall'eccezione quando l'oggetto viene istanziato, ad esempio:

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?
    }
}
    
posta dabadaba 16.04.2016 - 23:18
fonte

2 risposte

6

È corretto delegare qualsiasi logica mediante la composizione se tale logica cambierà dinamicamente durante l'esecuzione di un programma. Le convalide complesse come quelle che spieghi sono valide come qualsiasi altra da delegare a un'altra classe tramite la composizione.

Ricorda che le convalide possono avvenire in momenti diversi.

L'istanziazione di un validatore concreto come nel tuo esempio è una cattiva idea perché accoppia la tua classe Event a quel particolare validatore.

Supponiamo che tu non stia utilizzando alcun framework DI.

È possibile aggiungere il validatore al costruttore o iniettarlo con un metodo setter. Suggerisco che un metodo creatore in una fabbrica istanzia sia l'evento che il validatore e poi lo passa al costruttore di eventi o con un metodo setValidator.

Ovviamente un'interfaccia Validator o una classe astratta dovrebbero essere scritte in modo che le tue classi dipendano da essa e non da alcun validatore concreto.

L'esecuzione del metodo di convalida nella funzione di costruzione può essere problematica in quanto tutti gli stati che si desidera convalidare potrebbero non essere ancora presenti.

Ti suggerisco di creta un'interfaccia valida e lascia che le tue classi la implementino, quell'interfaccia potrebbe avere un metodo validate ().

In questo modo i componenti superiori della tua applicazione chiamano il metodo di validazione a volontà (che a sua volta è delegato al membro del validatore).

== > 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);
            }
        }
    }
}
    
risposta data 17.04.2016 - 00:37
fonte
4

I forget about absolutely any pre-check when using my model classes' setters and similar methods to add data

Questo è il problema. Idealmente dovresti evitare che i tuoi oggetti abbiano uno stato non valido: non consentire la creazione di istanze con stato non valido e se devi avere setter e altri metodi di modifica dello stato, lanciare un'eccezione proprio lì.

instead I let validation classes handle the validation.

Questo non è contraddittorio. Se la logica di convalida è sufficientemente complessa da giustificare la propria classe, è comunque possibile delegare la convalida. E se capisco che hai ragione, questo è esattamente quello che stai facendo ora:

Then, before instantiating a validable object, I will execute the validation to prevent illegal values.

Se hai ancora setter che potrebbero generare uno stato non valido, assicurati di convalidare prima in realtà cambiando stato. In caso contrario, visualizzerai un messaggio di errore, ma lascerai il tuo oggetto con stato non valido. Ancora: impedisce agli oggetti di avere uno stato non valido

Also, should I use boolean returning validation methods? Or just throw an exception if the validation fails? It seems to me the best option would be boolean returning methods and throw the exception when the object is instantianted

Mi suona bene, poiché il validatore si aspetta input validi o non validi quindi dal suo punto di vista, l'input non valido non è un'eccezione.

Aggiornamento: Se "il sistema nel suo complesso" non è valido, il motivo è che alcuni oggetti devono essere cambiati (ad esempio un giocatore) e qualche oggetto di livello superiore (ad esempio un evento) che fa riferimento questo oggetto ha una condizione che non è più soddisfatta. Ora una soluzione sarebbe quella di non consentire modifiche dirette al giocatore che potrebbero rendere qualcosa di non valido a un livello più alto. Qui è dove aiuto oggetti immutabili . Quindi un giocatore cambiato è un oggetto diverso. Una volta che un giocatore è associato a un evento, puoi solo cambiarlo dall'evento stesso (perché dovrai cambiare il riferimento a un nuovo oggetto) e di nuovo convalidarlo immediatamente.

    
risposta data 17.04.2016 - 15:15
fonte

Leggi altre domande sui tag