Modifier

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 !

Article suivant Article précédent

Blog Comments powered by Disqus.