Design Pattern Strategy - Un exemple concret avec la validation de formulaire
Salut à tous,
On m'a toujours dit que la meilleur utilisation à faire des Design patterns n'était pas de concevoir directement avec, mais que lorqu'un problème de conception se posait, voir si il n'y avait pas déjà un Design Pattern pouvant nous aider à le traiter. Mais comme tout ceux qui lisent pour la première fois Design Patterns Head First ou Essential ActionScript
, j'ai voulu tester les design patterns en cherchant a les appliquer directement à ma conception d'application.
Heuresement le temps est parfois un grand allié et il nous apporte un peu de sagesse et d'expérience. J'avais donc garder de côté ma liste de Design pattern avec une vague impression d'imaginer ce à quoi ils pouvaient servir. Et puis ce matin, ping (c'est le bruit de l'illumination). Un simple petit jeu concours à réaliser. Rien de bien complexe. Juste des champs de formulaire à valider. Je me suis dit autant en profiter pour faire une petite classe de validation de formulaire rapide et réutilisable.
J'ai donc commencer une classe utils avec des méthodes statiques qui permettraient de valider différents champs tels que E-mail, N° de téléphone, Code postal etc...
Mais bon ça ne me plaisait pas trop, tout ça était un peu lourd. Et là je me suis souvenu du pattern Strategy qui permet de déléguer un algorithme. Ca semblait la bonne solution à mon problème de conception trop lourde à maintenir.
Voilà donc un exemple de ce que ça donne :
D'abord une interface qui va définir la méthode indiquant si un champ est valide ou non :
/** * @author Benoit MILGRAM */ interface com.nodule.validator.IValidator { public function isValid( s : String ) : Boolean; }
La méthode isValid reçoit une chaîne et indique si elle est valide ou pas.
Ensuite, on créé différentes stratégies selon ce que l'on veut valider :
Une adresse email :
import com.nodule.validator.IValidator; import com.nodule.utils.StringUtil; /** * @author Benoit MILGRAM */ class com.nodule.validator.EmailValidator implements IValidator { public function EmailValidator() { } public function isValid( s : String ) : Boolean { return (( StringUtil.contains( s, "@") ) && ( StringUtil.contains( s, "."))); } }
Un code postal :
import com.nodule.validator.IValidator; /** * @author Benoit MILGRAM */ class com.nodule.validator.ZipCodeValidator implements IValidator { private static var ZIPCODE_LENGTH : Number = 5; public function ZipCodeValidator() { } public function isValid( s : String ) : Boolean { return ( ( Number( s ) != NaN ) && ( s.length == ZIPCODE_LENGTH) ); } }
Une date :
import com.nodule.validator.IValidator; import com.nodule.utils.NumberUtil; /** * @author Benoit MILGRAM */ class com.nodule.validator.DateValidator implements IValidator { private static var DATE_LENGTH : Number = 10; private static var DATE_RANGE_LIST : Array = [ [1, 31], [1, 12], [1900, 2007] ]; private static var DATE_LENGHT_LIST : Array = [ 2, 2, 4 ]; public function DateValidator() { } public function isValid(s : String) : Boolean { if( s.length != DATE_LENGTH ) { return false; } else { var dateArray : Array = s.split("/"); var dateArray_l:Number = dateArray.length; for (var i : Number = 0; i < dateArray_l; i++) { var date : String = dateArray[i]; if( Number( date ) != NaN ) { if( date.length == DATE_LENGHT_LIST[i] ) { if( ! NumberUtil.isInRange( Number( date ), DATE_RANGE_LIST[i][0], DATE_RANGE_LIST[i][1] ) ) { return false; } } else { return false; } } else { return false; } } } return true; } }
Pour finir, j'ai créé une classe Validator qui implémente elle même l'interface et qui va recevoir en argument du constructeur la classe ayant la stratégie adéquat :
import com.nodule.validator.IValidator; /** * @author Benoit MILGRAM */ class com.nodule.validator.Validator implements IValidator { private var _oValidatorStrategy : IValidator; public function Validator( strategy : IValidator ) { _oValidatorStrategy = strategy; } public function isValid( s : String ) : Boolean { return _oValidatorStrategy.isValid( s ); } }
Ainsi pour valider le contenu d'un champ texte nomé tiEmail et qui ait censé contenir un email :
import com.nodule.validator.Validator; import com.nodule.validator.EmailValidator; var validateEmail : IValidator = new Validator( new EmailValidator() ); validateEmail.isValid( tiEmail.text);
On peut rapidement et facilement maintenir notre code. Chaque classe ayant la responsabilité de valider un type d'information. Et on peut rapidement et facilement ajouter de nouveau type de validation sans que notre classe Validator ait besoin d'être modifié !
limpide …
Très intéressant, je connaissais ce design pattern, mais je ne voyais pas trop de contexte pour l’utiliser.
Merci, ca fait un bel exemple d’utilisation.
Exemple clair de l’utilisation de ce pattern.
Mais je me pose une quesion quand à ta façon de l’implémenter. Ne serait-ce pas une bonne chose d’ajouter un setteur à ta classe Validator pour pouvoir changer à la voler la startégie à utiliser ?
Il me semble d’ailleurs que c’est jsutement une des concepts de ce pattern, de pouvoir à tous moments changer de stratégies. Ainsi tu peux garder une même instance de ta classe Validator, et aux besoins (type de champs) changer de stratégie.
Salut Sunshine et merci de passer par ici.
Effectivement, c’est une implémentation intéressante et qui peut facilement être mise en place.
Après c’est implémentation, très bonne, du reste ne fait pas partie, il me semble, de celle de base du GoF. Mais ces patterns n’étant pas des objets figés, je trouve ta remarque très pertinentes
Salut Flap Flap,
Ok, ma remarque ne repose effectivement sur aucune base du GoF et j’avoue que de tête je ne me souviens pas du chapitre concernant ce pattern avec exactitude du livre "Design pattern" tête la première. Va falloir que je me replonge un coup dedans.
Je n’ai pas encore eu l’occasin d’utiliser ce pattern et donc de vérifier de la justesse de ma remarque. Si un jours tu l’utilises à nouveau et que tu fais un test avec un setteur tiens nous au courant pour nous dire les avantages et inconviennent que cela pose.
Merci
Ok ça marche.
Je pense que l’avantage rédie dans le cas où on aurait besoin d’une seule instance d’un objet (Singleton par ex. ).
Pour mon exemple celà permettrait d’avoir une classe Validator dont l’instance serait stockée et à laquelle on changerait la Stratégie avant chaque validation.
Dans l’exemple que j’ai pris ça n’a pas d’intérêt particulier mais je pense qu’on peux tomber sur des cas où ça l’a.