Article complet
Cet article et deux autres ont été utilisés pour rédiger un article beaucoup plus complet. La lecture de l'article complet rend donc obsolète ce billet et les suivants :
L'article : Les procédures internes et les closures : une nouvelle manière de coder en Windev.
Avant-propos
Cet article concerne la programmation avec l'AGL Windev et présente une notion avancée dans l'utilisation du WLangage. Bien que le sujet soit extrêmement intéressant, il est utile d'avoir quelques notions en programmation pour le comprendre.
De plus, cet article traite des procédures internes qui sont apparues à partir de Windev 20. Les versions 19 et antérieures ne sont donc malheureusement pas concernées.
Les pseudos-class
A ma connaissance, ce terme n'existe pas dans le domaine lexicale de Windev, c'est donc une invention de ma part. Il faut faire attention de ne pas le confondre avec les pseudo-classes de CSS.
Cet article est la suite directe de l'article sur les closures que je vous invite à lire si ce n'est pas déjà fait : Windev - Ouverture aux closures.
Le principe des pseudos-classes est le même qu'avec les closures, sauf qu'au lieu de renvoyer une fonction, on renvoie une structure dont au moins un des membres est de type procédure.
Voici un exemple :
J'ai la structure suivante :
stAnimal est une Structure
Manger est une Procédure
FaireDuBruit est une Procédure
Poids est un entier
FIN
La fonction suivante :
PROCEDURE CréerAnimal(LOCAL pBruit est une chaîne, pPoids est un entier)
UnAnimal est un stAnimal dynamique = allouer un stAnimal
UnAnimal.Poids = pPoids
PROCEDURE INTERNE iFaireDuBruit()
Trace(pBruit)
FIN
UnAnimal:FaireDuBruit = iFaireDuBruit
PROCEDURE INTERNE iManger()
UnAnimal.poids += 1
FIN
UnAnimal:Manger = iManger
RENVOYER UnAnimal
Et l'appel suivant :
UnChien est un stAnimal dynamique = CréerAnimal("Ouaf",15)
UnChat est un stAnimal dynamique = CréerAnimal("Miaou",3)
UnChien.Manger()
UnChien.Manger()
UnChien.Manger()
UnChat.Manger()
UnChat.Manger()
UnChat.Manger()
UnChat.Manger()
UnChat.Manger()
Trace("Poids du chien", UnChien.Poids)
Trace("Poids du chat", UnChat.Poids)
UnChien.FaireDuBruit()
UnChat.FaireDuBruit()
La trace affichera :
Poids du chien 18
Poids du chat 8
Ouaf
Miaou
Les règles à suivre
Comme déjà dit, la règle la plus importante, c'est que la structure contienne au moins un membre de type procédure. C'est ce membre qui utilisera le principe de la closure en référençant la fonction interne définie dans la fonction.
La seconde règle très importante, c'est de rendre dynamique les objets. Sans cela, on perd la mémorisation de l'état de l'instance. Dans l'exemple, le poids de l'animal ne change pas si on appelle la procédure Manger d'une instance non dynamique.
Aller plus loin
L'exemple est assez simple, il est possible d'aller encore plus loin.
Tout d'abord, il est possible de rajouter des paramètres aux procédures. La complétion automatique de Windev n'aidera pas, mais les paramètres seront passés à la procédure interne.
Ensuite, il est possible de faire une sorte d'héritage. En faisant évoluer la structure, on peut rajouter de nouvelles fonctions et de nouveaux membres. La fonction de création (qui fait office de constructeur) peut elle aussi appeler une autre fonction de création (héritage de constructeur).
On peut aussi choisir quelle procédure on va attacher à chaque membre de type procédure. Avec ce mécanisme, on peut simuler une dérivation des méthodes. Ou créer un objet entièrement paramétrable. C'est au choix.
Aller encore plus loin
Tout d'abord, merci à @GuillaumeBayle qui m'a donné cette idée.
Il est possible de passer des procédures ou du code source à compiler dynamiquement en paramètre à la fonction constructeur de la pseudo-classe. Trois schémas possibles pour la structure suivante :
stAnimal est une Structure
Bruit est une chaine
FaireDuBruit est une Procédure
FIN
Avec du code compilé dynamiquement et la fonction Compile
Le constructeur :
PROCEDURE CréerAnimal(pCodeFaireDuBruit)
UnAnimal est un stAnimal dynamique = allouer un stAnimal
UnAnimal:FaireDuBruit = Compile(pCodeFaireDuBruit)
RENVOYER UnAnimal
L'appel du constructeur : UnChien est un stAnimal dynamique = CréerAnimal("Trace(""Ouaf"")") UnChat est un stAnimal dynamique = CréerAnimal("Trace(""Miaou"")")
UnChien.FaireDuBruit() // La trace affiche Ouaf
UnChat.FaireDuBruit() // La trace affiche Miaou
Avec du code exécuté dynamiquement et la fonction ExécuteCode
L'avantage de cette méthode est de pouvoir utiliser l'objet animal défini dans la procédure CréerAnimal.
Le constructeur :
PROCEDURE CréerAnimal(LOCAL pBruit, LOCAL pCodeFaireDuBruit)
UnAnimal est un stAnimal dynamique = allouer un stAnimal
UnAnimal.Bruit = pBruit
PROCEDURE INTERNE iFaireDuBruit()
ExécuteCode(pCodeFaireDuBruit)
FIN
UnAnimal.FaireDuBruit = iFaireDuBruit
RENVOYER UnAnimal
L'appel du constructeur :
UnChien est un stAnimal dynamique = CréerAnimal("Ouaf", "Trace(""Je suis un chien qui dit "" + UnAnimal.Bruit)")
UnChat est un stAnimal dynamique = CréerAnimal("Miaou", "Trace(""Je suis un chat qui dit "" + UnAnimal.Bruit)")
UnChien.FaireDuBruit() // La trace affiche Je suis un chien qui dit ouaf
UnChat.FaireDuBruit() // La trace affiche Je suis un chat qui dit miaou
Directement avec une autre procédure
L'avantage de cette méthode est de pouvoir utiliser les variables UnChien ou UnChat dans la procédure
Le constructeur : PROCEDURE CréerAnimal(LOCAL pBruit, LOCAL pFaireDuBruit est une Procédure) UnAnimal est un stAnimal dynamique = allouer un stAnimal UnAnimal.Bruit = pBruit
UnAnimal.FaireDuBruit = pFaireDuBruit
RENVOYER UnAnimal
L'appel du constructeur :
UnChien est un stAnimal dynamique = CréerAnimal("Ouaf", iChienFaireDuBruit)
UnChat est un stAnimal dynamique = CréerAnimal("Miaou", iChatFaireDuBruit)
PROCEDURE INTERNE iChienFaireDuBruit()
Trace("Le chien fait : " + UnChien.Bruit)
FIN
PROCEDURE INTERNE iChatFaireDuBruit()
Trace("Le chat fait : " + UnChat.Bruit)
FIN
UnChien.FaireDuBruit() // La trace affiche Le chien fait Ouaf
UnChat.FaireDuBruit() // La trace affiche Le chat fait Miaou
Et pourquoi pas une closure ?
Le système de closure est décrit dans l'article précédent : Windev - Ouverture aux closures
Le constructeur est identique à l'exemple précédent. Par contre, l'appel diffère :
PROCEDURE INTERNE iClosureFaireDuBruit(LOCAL pAnimal , LOCAL pBruit)
PROCEDURE INTERNE iFaireDuBruit()
Trace(ChaîneConstruit("Le %1 fait : %2",pAnimal, pBruit))
FIN
FaireDuBruit est une Procédure = iFaireDuBruit
RENVOYER FaireDuBruit
FIN
UnChien est un stAnimal dynamique = CréerAnimal("Ouaf", iClosureFaireDuBruit("chien", "Ouaf"))
UnChat est un stAnimal dynamique = CréerAnimal("Miaou", iClosureFaireDuBruit("chat", "Miaou"))
UnChien.FaireDuBruit() // La trace affiche Le chien fait : Ouaf
UnChat.FaireDuBruit() // La trace affiche Le chat fait : Miaou
Les limites
Je n'ai identifié qu'une seule limite pour le moment, mais un tel objet ne peut pas avoir de destructeur implicite. Il explicitement le mettre en place.
Pour résumer
Cette méthode ne remplace pas les classes Windev qui resteront toujours plus puissantes. Mais lorsqu'on a besoin d'avoir une multitude d'objets simples mais différents, cette pratique permet d'éviter de créer 36000 fichiers (et oui, dans Windev, une classe = un fichier). De plus, la portée de ces structures dépend de l'endroit où on les déclare. Tandis qu'une classe est globale au projet, une pseudo-classe peut exister seulement au sein d'une fonction ou d'une fenêtre.
Merci pour votre lecture.
Cet article vous a intéressé ? Vous voulez en savoir plus ? Suivez le lien !