WinDev et le Design pattern : Stratégie

Design pattern de comportement : Stratégie

V. Formet - dsr57

Cet article explique la mise en application du Design pattern stratégie en WinDev.

Vous pouvez commenter cet article sur le post dédié : Commentez ♪

- Titre : WinDev et le Design pattern : Stratégie

- Auteur : V. Formet - dsr57

- Parution : 23 juin 2014

- Public visé :

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2014 V. Formet - dsr57. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Définition de design pattern

- II. Le design pattern Stratégie

- II-A. Description du problème

- II-B. La solution

- III. Programmation du design pattern

- III-A. Le jeu super canard

- III-A-1. La première version

- III-A-2. Les évolutions…

- III-A-3. Héritage multiple

- III-A-4. La solution

- IV. Remerciements

I - Définition de design pattern

Le premier article sur le Design pattern singleton donne une définition des design patterns (ou encore « patron de conception » en français), il en existe trois types :

- construction : ils définissent comment faire l'instanciation et la configuration des classes et des objets ;

- structure : ils définissent comment organiser les classes d'un programme dans une structure plus large (séparant l'interface de l'implémentation) ;

- comportement : ils définissent comment organiser les objets pour que ceux-ci collaborent (distribution des responsabilités) et expliquent le fonctionnement des algorithmes impliqués.

Dans cet article, nous allons voir la mise en place du design pattern Stratégie de type comportemental.

II - Le design pattern Stratégie

Le design pattern Stratégie définit un objet représentant un algorithme dédié à une tâche spécifique.

II-A - Description du problème

En programmation objet, une classe est composée :

- d'attributs représentant l'état des objets ;

- de méthodes représentant leur comportement.

Lors de l'identification d'une nouvelle tâche, le premier réflexe est de créer une classe représentant et gérant celle-ci. À l'occasion de demandes clients et des évolutions techniques, cette tâche est amenée à évoluer, on développe une notion d'héritage en complexifiant le modèle d'origine (ajout de méthodes et de membres). Les méthodes (comportements) peuvent être communes à plusieurs classes, sous-classes et complexifient encore plus le diagramme de classes. Dans notre problématique, nous devons prendre en compte que toute modification de l'existant peut engendrer des modifications de comportement et donc des régressions.

II-B - La solution

Avant de donner la solution clé en main, nous allons lister les critères que la solution doit respecter :

- éviter la duplication de code ;

- encapsuler les différences ;

- faciliter les évolutions ;

- éviter l'effet de bord.
Et particulièrement pour ce design pattern  :

- utiliser la composition plus que l'héritage ;

- créer des superclasses de type comportemental ;

- permettre une évolution du comportement en cours de programme.

III - Programmation du design pattern

Après la théorie, passons à la pratique avec la mise en place du design pattern Stratégie au travers d'un exemple adapté du livre « Design Pattern - la tête la première », il permettra de voir l'évolution progressive d'un programme et les problèmes rencontrés et l'intérêt du design pattern Stratégie.

III-A - Le jeu super canard

III-A-1 - La première version

Le programme de base est un jeu de canards évoluant dans une mare, se déplaçant et émettant des bruits. Dans la première version, plusieurs types de canards existent :

- les colverts ;

- les mandarins.
Les développeurs ont conçu le programme en orienté objet, avec une super classe ac_canard .

Classe ac_canard

ac_canard est une Classe
//Membres
FIN
PROCEDURE PROTÉGÉE Constructeur()
PROCEDURE Destructeur()

PROCEDURE cancaner()
RENVOYER "Je suis un canard et je cancane"

PROCEDURE nager()
RENVOYER "Je suis un canard et je nage"

PROCEDURE PROTÉGÉE afficher()
RENVOYER ""

La classe de base gère les actions communes aux canards (nager et cancaner).

La procédure afficher() est protégée, car celle-ci doit être redéfinie dans toutes les classes filles.

Et deux classes pc_colvert et pc_mandarin représentant les canards.

classe pc_colvert

pc_colvert est une Classe
hérite de ac_canard
FIN

PROCEDURE Constructeur()
PROCEDURE Destructeur()

PROCEDURE VIRTUELLE afficher()
RENVOYER "Afficher un colvert"

classe pc_mandarin

pc_mandarin est une Classe
hérite de ac_canard
FIN

PROCEDURE Constructeur()
PROCEDURE Destructeur()
// Résumé : <indiquez ici ce que fait la procédure>

PROCEDURE VIRTUELLE afficher()
RENVOYER "Affiche un mandarin"

Suivant le même principe on peut mettre en place d'autres canards.

Les classes filles implémentent leur méthode afficher(). Effectivement, ce sont tous des canards mais ils ont un aspect visuel différent.

La première étape de conception est réalisée et la première version du jeu est finalisée.

III-A-2 - Les évolutions…

Dans la deuxième version, une évolution doit être proposée : la prise en compte de canards volants. Comme le développement a été effectué en objet, cela ne devrait pas poser de problèmes.

Dans le code on ajoute une fonction voler() dans la classe ac_canard.

Méthode voler de la classe ac_canard

PROCEDURE voler()
RENVOYER « Je suis un canard volant »

Avec l'implémentation de la fonction voler() dans la super classe, l'ensemble des sous-classes héritent de cette évolution et tous les canards volent. Les premiers tests révèlent un problème : tous les canards volent. Lors de la création de la fonction, on a oublié de prendre en compte que certains canards comme les canards en plastique ne volent pas.

Par exemple la classe pc_canardEnPlastique :

Classe pc_CanardEnPlastique

pc_canardEnPlastique est une Classe
hérite de ac_canard
FIN

PROCEDURE Constructeur()
PROCEDURE Destructeur()

PROCEDURE VIRTUELLE cancaner()
RENVOYER "Je suis un canard et je couine"

Dans cette classe, on a déjà redéfini la méthode cancaner(), on peut donc redéfinir la méthode voler().

Méthode voler de la classe pc_CanardEnPlastique

PROCEDURE VIRTUELLE voler()

RENVOYER "Je suis un canard qui ne vole pas"

Toujours suivant le même principe, on peut avoir des canards en bois représentés par la classe suivante :

Classe pc_canardEnBois

pc_canardEnBois est une Classe
hérite de ac_canard
FIN

PROCEDURE Constructeur()
PROCEDURE Destructeur()

PROCEDURE VIRTUELLE voler()
RENVOYER "Je suis un canard qui ne vole pas"

III-A-3 - Héritage multiple

Dans la vie de notre jeu, on sait que les spécifications vont changer, il nous faudra redéfinir les méthodes voler() et cancaner()pour toutes les nouvelles sous-classes ajoutées au programme avec un comportement contraint par la super classe. Une nouvelle conception plus judicieuse et plus compréhensible doit être adoptée pour permettre à certains canards (pas tous) de voler et de cancaner.

Une solution est d'extraire les méthodes cancaner et voler des superclasses ac_volant et ac_cancaneur avec respectivement, les méthodes voler() et cancaner(). Avec l'héritage multiple, les sous-classes hériteront et redéfiniront les comportements souhaités.

Dans les langages tels que Java et .NET, l'héritage multiple est remplacé par des interfaces. L'utilisation d'héritage multiple est une solution de contournement.

Développons cet aspect :

Classe ac_volant

ac_volant est une Classe
FIN

PROCEDURE Constructeur()
PROCEDURE Destructeur()

PROCEDURE voler()
//Méthode à redéfinir dans les sous-classes

classe ac_cancaneur

ac_cancaneur est une Classe
FIN

PROCEDURE Constructeur()
PROCEDURE Destructeur()

PROCEDURE cancaner()
//Méthode à redéfinir dans les sous-classes

On modifie la classe ac_canard pour ne conserver que la méthode afficher() et nager() et on la met en place dans les sous-classes.

Codes des sous-classes mises à jour pc_colvert et pc_mandarin :

classe pc_colvert

pc_colvert est une Classe
hérite de ac_canard
hérite de ac_volant
hérite de ac_cancaneur
FIN

PROCEDURE Constructeur()
PROCEDURE Destructeur()

PROCEDURE VIRTUELLE afficher()
RENVOYER "Afficher un colvert"

PROCEDURE VIRTUELLE voler()
RENVOYER "Je suis un canard et je vole"

PROCEDURE VIRTUELLE cancaner()
RENVOYER "Je suis un canard et je cancane"

classe pc_mandarin

pc_mandarin est une Classe
hérite de ac_canard
hérite de ac_volant
hérite de ac_cancaneur
FIN

PROCEDURE Constructeur()
PROCEDURE Destructeur()

PROCEDURE VIRTUELLE afficher()
RENVOYER "Affiche un mandarin"

PROCEDURE VIRTUELLE cancaner()
RENVOYER "Je suis un canard et je cancane"

PROCEDURE VIRTUELLE voler()
RENVOYER "Je suis un canard et je vole"

Après la mise à jour des deux sous-classes, on se rend compte que cette conception duplique du code et ne respecte pas un critère, avec pour conséquences :

- d'empêcher la réutilisabilité ;

- de compliquer la maintenance.

Dans cette solution nous avons utilisé des classes abstraites comme des interfaces, c'est-à-dire sans code. Le code des méthodes pourrait être implémenté dans les fonctions voler() et cancaner(), mais cette solution ne serait pas pérenne en cas d'évolution nécessitant deux possibilités différentes de voler. Il faudrait alors repasser sur toutes les sous-classes pour prendre en compte ou pas cette nouvelle possibilité…

Pour faciliter l'évolution, la conception d'un logiciel doit prendre en charge le changement.

III-A-4 - La solution

Nous avons vu dans les paragraphes précédents :

- l'héritage : avec un diagramme de classes se complexifiant rapidement ;

- l'héritage multiple : solution prometteuse avec l'extraction des comportements, mais un nouveau comportement, demande une recherche dans toutes les sous-classes et une modification de certaines.
Dans toute idée il n'y a pas que du mauvais, l'héritage multiple est un début de réflexion sur la séparation des comportements.

La solution est de séparer les aspects qui varient de ceux qui sont constants, une définition plus informaticienne : extraire les parties variables et les encapsuler pour permettre de les modifier ou de les augmenter sans affecter celles qui ne varient pas. Mettons en place cette solution dans l'exemple de nos canards. Ce qui nous pose problème depuis le départ ce sont les procédures voler()et cancaner(). Nous allons créer deux ensembles de classes, chacun contiendra les implémentations des classes des comportements respectifs et les procédures voler()et cancaner().

Pour le comportement de cancaner nous aurons les classes suivantes :

- pc_cancaner ;

- pc_couiner ;

- pc_silence.

Pour le comportement voler nous aurons les classes suivantes :

- pc_volerAvecAiles;

- pc_nePasVoler.

Nous allons représenter les comportements cancaner et voler en deux classes abstraites :

- ac_comportementVoler ;

- ac_comportementCancaner.

et chaque classe comportementale héritera d'une de ces classes.

Dans un langage tel que JAVA et .Net, il est préférable d'utiliser les interfaces au lieu des classes abstraites. L'idée est de mettre en place une conception pour utiliser le principe de polymorphisme.

À la différence avec nos essais précédents, les comportements provenaient de la classe ac_canard où étaient redéfinis dans les sous-classes. Avec cette conception, les sous-classes de canard auront un comportement représenté par les classes abstraites ac_cancaner et ac_voler, tel que le comportement ne soit pas dans les sous-classes.

Codes des superclasses comportementales :

classe ac_comptementCancaner

ac_comportementCancaner est une Classe
FIN

PROCEDURE Constructeur()
PROCEDURE Destructeur()

PROCEDURE cancaner()
RENVOYER ""
//A REDEFINIR DANS LES SOUS-CLASSES COMPORTEMENTALES

classe ac_comportementVoler

ac_comportementVoler est une Classe
FIN

PROCEDURE Constructeur()
PROCEDURE Destructeur()

PROCEDURE voler()
RENVOYER ""
//A REDEFINIR DANS LES SOUS-CLASSES COMPORTEMENTALES

Les codes des sous-classes du comportement voler doivent implémenter uniquement voler() :

pc_volerAvecAiles

pc_volerAvecAiles est une Classe
hérite de ac_comportementVoler
FIN

PROCEDURE Constructeur()
PROCEDURE Destructeur()

PROCEDURE VIRTUELLE voler()
RENVOYER "Je suis un canard qui vole avec des ailes"

pc_nePasVoler

pc_nePasVoler est une Classe
hérite de ac_comportementVoler
FIN

PROCEDURE Constructeur()
PROCEDURE Destructeur()

PROCEDURE VIRTUELLE voler()
RENVOYER "je suis un canard qui ne vole pas"

Les codes des sous-classes du comportement cancaner doivent implémenter uniquement cancaner() :

Classe pc_cancaner

pc_cancaner est une Classe
hérite de ac_comportementCancaner
FIN

PROCEDURE Constructeur()
PROCEDURE Destructeur()

PROCEDURE VIRTUELLE cancaner()
RENVOYER "Je suis un canard et je cancane"

classe pc_couiner

pc_couiner est une Classe
hérite de ac_comportementCancaner
FIN

PROCEDURE Constructeur()
PROCEDURE Destructeur()

PROCEDURE VIRTUELLE Cancaner()
RENVOYER "Je suis un canard qui couine"

classe pc_muet

pc_couiner est une Classe
hérite de ac_comportementCancaner
FIN
PROCEDURE Constructeur()
PROCEDURE Destructeur()

PROCEDURE VIRTUELLE Cancaner()
RENVOYER "Je suis un canard qui couine"

Avec cette conception, on peut réutiliser les comportements pour d'autres objets et on peut créer de nouveaux comportements sans toucher aux existants et aux classes canards. L'autre intérêt de cette conception, la classe ac_canard va déléguer les comportements. Maintenant que nous avons programmé nos comportements, nous allons créer la liaison entre la classe ac_canard et les comportements avec les opérations suivantes :

- création de deux nouveaux membres de type superclasses comportementales ;

- création de deux méthodes cancaner() et voler() faisant appel aux méthodes des superclasses et avec le polymorphisme les méthodes des classes comportementales.

Classe ac_canard

ac_canard est une Classe
lo_comportementCancaner est un ac_comportementCancaner dynamique
lo_comportementVoler est un ac_comportementVoler dynamique
FIN

PROCEDURE PROTÉGÉE Constructeur()
PROCEDURE Destructeur()

PROCEDURE PROTÉGÉE afficher()
RENVOYER ""

PROCEDURE VIRTUELLE voler()
RENVOYER :lo_comportementVoler:voler()

PROCEDURE VIRTUELLE cancaner()
RENVOYER :lo_comportementCancaner:cancaner()

Dans les méthodes cancaner()et voler(),nous observons la délégation des comportements de la classe ac_canard. La dernière chose à mettre en place est l'affectation des comportements aux canards.

Dans le constructeur des sous-classes canard on alloue les comportements :

Classe pc_colvert

pc_colvert est une Classe
hérite de ac_canard
FIN

PROCEDURE Constructeur()
:lo_comportementCancaner=allouer un pc_cancaner()
:lo_comportementVoler=allouer un pc_volerAvecAiles()

PROCEDURE Destructeur()

PROCEDURE VIRTUELLE afficher()
RENVOYER "Afficher un colvert"

Dans le même principe, vous pouvez coder les autres sous-classes deac_canard.

Maintenant, testons notre conception et notre programmation :

programme de test

lo_canard est pc_colvert()

Trace(lo_canard:afficher())
Trace(lo_canard:cancaner())
Trace(lo_canard:voler())

résultat du test

Afficher un colvert
Je suis un canard et je cancane
Je suis un canard qui vole avec des ailes

Si vous ne constatez pas le même résultat, vérifiez que vous avez rajouté le mot-clé « VIRTUELLE » dans la définition des procédures surchargées.

Faisons le point sur les critères devant respecter la solution :

- éviter la duplication de code ;

- encapsuler les différences ;

- faciliter les évolutions ;

- éviter l'effet de bord ;

- utiliser la composition plus que l'héritage ;

- créer des superclasses de type comportemental ;

- permettre une évolution du comportement en cours de programme.

Nous avons respecté tous les critères sauf le dernier, l'évolution du comportement. Effectivement, on alloue un comportement dans le constructeur des sous-classes de ac_canard, mais on n'a pas prévu l'évolution du changement de comportement en cours de programme. Afin de mettre en place ce critère, nous allons créer deux méthodes dans la classe ac_canard :

- setComportementVoler () ;

- setComportementCancaner ().

Ces méthodes permettent de changer dynamiquement le comportement des canards.

Code de ces méthodes :

code des méthodes

PROCEDURE setComportementVoler(po_comportementVoler est un ac_comportementVoler dynamique)
:lo_comportementVoler=po_comportementVoler

PROCEDURE setComportementCancaner(po_ComportementCancaner est un ac_comportementCancaner dynamique)
:lo_comportementCancaner=po_ComportementCancaner

Dans ces fonctions, on passe en paramètre le nouveau comportement et on l'affecte au membre.

Reprenons notre précédent test et ajoutons une modification de comportement.

Test

lo_canard est pc_colvert()

Trace(lo_canard:afficher())
Trace(lo_canard:cancaner())
Trace(lo_canard:voler())

lo_canard:setComportementVoler(allouer un pc_nePasVoler())
Trace("---- Changement de comportement")
Trace(lo_canard:voler())

Résultat du test

Afficher un colvert
Je suis un canard et je cancane
Je suis un canard qui vole avec des ailes
---- Changement de comportement
je suis un canard qui ne vole pas

IV - Remerciements

- relecture technique : LittleWhite  ;

- relecture orthographique : f-leb  ;