Lorsque j'ai beaucoup de données à valider, dois-je créer une nouvelle classe dans le seul but de la validation ou dois-je m'en tenir à la validation dans la méthode?
Mon exemple particulier envisage un tournoi et une classe d'événement/catégorie: Tournament
et Event
, qui modélise un tournoi sportif et chaque tournoi a une ou plusieurs catégories.
Il y a toutes sortes de choses à valider dans ces classes: les joueurs doivent être vides, doivent être uniques, le nombre de matchs que chaque joueur doit jouer, le nombre de joueurs que chaque match a, des matchs prédéfinis et un très gros etcetera, y compris beaucoup plus règles complexes.
Il y a aussi certaines parties que je dois valider dans son ensemble, comme la façon dont les classes s'intègrent les unes aux autres. Par exemple, la validation unitaire d'un Player
peut être très bien, mais si un événement a le même joueur deux fois, c'est une erreur de validation.
Alors qu'en est-il?: J'oublie absolument toute pré-vérification lorsque j'utilise les setters de mes classes de modèles et des méthodes similaires pour ajouter des données et à la place je laisse les classes de validation s'en occuper.
Nous aurons donc quelque chose comme EventValidator
avec un Event
comme membre, et une méthode validate()
qui valide tout l'objet, ainsi que des méthodes singulières pour valider les règles de tous les membres.
Ensuite, avant d'instancier un objet validable, j'exécuterai la validation pour empêcher les valeurs illégales.
Ma conception est-elle correcte? Dois-je faire quelque chose différemment?
De plus, dois-je utiliser des méthodes de validation de retour booléen? Ou simplement lever une exception si la validation échoue? Il me semble que la meilleure option serait de renvoyer des méthodes booléennes et de lever l'exception lorsque l'objet est instancié, par exemple:
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?
}
}
Il est OK de déléguer n'importe quelle logique au moyen de la composition si cette logique va changer dynamiquement pendant l'exécution d'un programme. Les validations complexes comme celles que vous expliquez sont aussi bonnes que n'importe quelle candidate à déléguer à une autre classe via la composition.
N'oubliez pas cependant que les validations peuvent se produire à différents moments.
Instancier un validateur concret comme dans votre exemple est une mauvaise idée car il couple votre classe d'événements à ce validateur particulier.
Supposons que vous n'utilisez aucun framework DI.
Vous pouvez ajouter le validateur au constructeur ou l'injecter avec une méthode setter. Je suggère qu'une méthode de créateur dans une usine instancie à la fois l'événement et le validateur, puis la transmet soit au constructeur de l'événement, soit avec une méthode setValidator.
Évidemment, une interface Validator et/ou une classe abstraite doivent être écrites afin que vos classes en dépendent et non d'un validateur concret.
L'exécution de la méthode de validation dans le constructeur peut être problématique car tout l'état que vous souhaitez valider n'est peut-être pas encore en place.
Je vous suggère de créer une interface validable et de laisser vos classes l'implémenter, cette interface pourrait avoir une méthode validate ().
De cette façon, les composants supérieurs de votre application appellent la méthode de validation à volonté (qui à son tour est déléguée au membre validateur).
==> 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);
}
}
}
}
J'oublie absolument toute pré-vérification lors de l'utilisation des setters de mes classes de modèles et des méthodes similaires pour ajouter des données
C'est le problème. Idéalement, vous devez empêcher vos objets d'avoir un état non valide: n'autorisez pas l'instanciation avec un état non valide et si vous devez avoir des setters et d'autres méthodes de changement d'état, jetez une exception juste là.
à la place, je laisse les classes de validation gérer la validation.
Ce n'est pas contradictoire. Si la logique de validation est suffisamment complexe pour justifier sa propre classe, vous pouvez toujours déléguer la validation. Et si je vous comprends bien, c'est exactement ce que vous faites maintenant:
Ensuite, avant d'instancier un objet validable, j'exécuterai la validation pour empêcher les valeurs illégales.
Si vous avez encore des setters qui pourraient entraîner un état non valide, assurez-vous de valider avant l'état en cours de changement. Sinon, vous aurez un message d'erreur, mais vous laisserez toujours votre objet avec un état non valide. Encore une fois: empêchez vos objets d'avoir un état invalide
De plus, dois-je utiliser des méthodes de validation de retour booléen? Ou simplement lever une exception si la validation échoue? Il me semble que la meilleure option serait de renvoyer des méthodes booléennes et de lever l'exception lorsque l'objet est instancié
Cela me semble bien, car le validateur attend une entrée valide ou non valide, donc de son point de vue, une entrée non valide ne fait pas exception.
pdate: Si "le système dans son ensemble" devient invalide, la raison en est qu'un objet doit avoir changé (par exemple un joueur) et un objet éventuellement de niveau supérieur (par exemple un événement) qui fait référence à cet objet a une condition qui n'est plus remplie. Désormais, une solution serait de ne pas autoriser de modifications directes du lecteur qui pourraient rendre quelque chose non valide à un niveau supérieur. C'est là que objets immuables aide. Un joueur changé est alors un objet différent. Une fois qu'un joueur est associé à un événement, vous ne pouvez le modifier qu'à partir de l'événement lui-même (car vous devrez changer la référence à un nouvel objet) et valider à nouveau immédiatement.