:: Enseignements :: Licence :: L3 :: 2016-2017 :: Programmation Objet avec Java ::
[LOGO]

Monad or not Monad ?


On s'intéresse à la partie validation d'une application web. La validation est l'étape qui consiste à vérifier que les données saisies par l'utilisateur sont valides (que le champ 'mot de passe' a la bonne longueur, que le champ 'nom' n'est pas vide, etc...).
On cherchera, dans un premier temps, à reporter une erreur dès qu'un champ est invalide. Puis dans un second temps on récupérera les différents messages d'erreur de champs invalides pour afficher un message à l'utilisateur indiquant toutes les erreurs de saisie.
Pour la suite, on pré-supposera que le code tourne sur un serveur d'applications et que lorsque l'utilisateur entre les différentes informations pour s’inscrire sur le site web, ces informations sont décodées par le serveur web et stockées dans un objet SignInForm dont le code est ci-dessous.

De plus, un de vos collègues (on va dire pas très réveillé) a écrit le code de validation suivant:

Exercice 1 - Validation

Sachant qu'il n'y a pas que ce formulaire sur le site web (il y a, par exemple, d'autres formulaires qui requièrent aussi la validation d'email), le chef de projet décide qu'il serait bon de factoriser un peu le code de la méthode validateForm en sortant les différentes validations sous forme de méthodes statiques (validEmail, validPassword, ...) dans une classe Validation dans le package fr.umlv.validation.

  1. Créer la classe Validation et modifier le code de validateForm pour utiliser les méthodes de validation.
    Note: on remarquera que tester si une chaîne n'est pas vide est équivalent à tester si sa longueur est supérieure à zéro.
  2. Modifier le code de sorte à ce qu'une seule exception soit levée, quel que soit le nombre d'erreurs.

Exercice 2 - Validation 2

Le directeur technique passant dans le coin, celui-ci pense que créer une classe FooFormValidator pour chaque formulaire est inutile. Il vous demande de créer une seule classe Validator qui pourra être utilisée pour valider tous les formulaires d'entrée.
Il propose d'écrire le code comme ceci
     SignInForm form = new SignInForm("foo@bar.com", "XXXXXXXXXXX", "bob", "foo", 20);
     Validator<SignInForm> validator = new Validator<>(form);
     validator.validate(signin -> Validation.validEmailAddress(signin.getEmail()));
   

  1. Écrire le code de la classe Validator correspondante.
  2. Le chef de projet fait remarquer que l'on peut utiliser le même mécanisme de builder que la classe StringBuilder pour chainer les appels
         SignInForm form = new SignInForm("foo@bar.com", "XXXXXXXXXXX", "bob", "foo", 20);
         Validator<SignInForm> validator = new Validator<>(form)
           .validate(signin -> Validation.validEmailAddress(signin.getEmail()))
           .validate(...)
        
    Modifier le code de la classe Validator en conséquence.
  3. Puis, votre chef de projet vous fait remarquer que cela serait sympa de séparer l'appel au getter de l'appel à la validation comme ceci:
         SignInForm form = new SignInForm("foo@bar.com", "XXXXXXXXXXX", "bob", "foo", 20);
         Validator<SignInForm> validator = new Validator<>(form)
           .validate(SignInForm::getEmail, Validation::validEmailAddress)
           .validate(...)
        
    Ajouter une nouvelle méthode validate (avec 2 paramètres) pour que le code ci-dessus fonctionne.
  4. Compléter le code donné dans l'énoncé de la question précédente pour faire également la validation du mot de passe.
    Pourquoi il n'est pas possible d'utiliser des method references pour référencer les autres méthodes de validation ?
    En fait, il est plus flexible de mettre les lambdas dans les méthodes Validation.validEmailAddress ou Validation.validPassword() qu'à l'endroit on l'on fait l'appel.
    Changer le code et les signatures des méthodes dans Validation pour que le code ci-dessous fonctionne.
         SignInForm form = new SignInForm("foo@bar.com", "XXXXXXXXXXX", "bob", "foo", 20);
         Validator<SignInForm> validator = new Validator<>(form)
           .validate(SignInForm::getEmail, Validation.validEmailAddress())
           .validate(SignInForm::getPassword, Validation.validPassword(10))
           .validate(...)
        
  5. Quel est le problème avec le getter getAge ?
    Comment corriger le problème ?
    Il y a deux solutions ! Quelles sont les avantages et les inconvénients ?
    Implanter la solution qui n'utilise qu'une seule méthode validate avec 2 paramètres.
  6. En fait, le code de la méthode validate avec 2 paramètres pourrait appeler le code de la méthode validate avec 1 seul paramètre. Comment faire ?
    Modifier le code en conséquence.
  7. Optionnel: Comment écrire le code de la question précédente avec des méthodes références et en utilisant la composition de fonctions?

Exercice 3 - Validation 3

Enfin, on souhaiterait associer à chaque validation un message d'erreur et ne lever l'exception (si il y a au moins une erreur de validation) qu'à la fin de toutes les validations avec l'ensemble des messages d'erreur.
En terme de code, on veut modifier le Validator pour que le code suivant fonctionne.
      SignInFrom validatedForm =
        new Validator<>(form)
          .validate(SignInForm::getEmail, Validation.validEmailAddress(), "email adress should contain a '@'")
          .validate(...)
          .get();  // lève un erreur ou renvoie le formulaire validé
   

  1. Modifier le code du Validator existant pour qu'il fonctionne avec l'exemple ci-dessus.
    Note: on sauvegardera les différents messages d'erreur, s'ils existent, dans une ArrayList.
  2. La classe Validator est-elle un monad ?