«
»
Grenier, boite 6

Grenier, boite 6

Le site http://www.poo.c.la proposait une introduction à la programmation orientée objet (POO) dès 2004, à un moment où l’on en parlait peu dans WinDev. Il a certainement aidé bon nombre de développeurs mais a récemment quitté internet.  Donc pour se souvenir de cette période et lire ces articles qui restent pertinents, voici le contenu qu’il a été possible de récupérer. Merci à son contributeur.

Les différents types de programmation

Programmation événementielle
La programmation événementielle est une programmation dans laquelle les traitements se déclenchent en fonction d’événements. Le concept est parfois un peu plus difficile à maîtriser lorsque l’on n’a pas l’habitude de ce type de programmation.

Heureusement avec WinDev, ce type de programmation est pratiquement transparent et en tout cas totalement intuitif (à mon goût). C’est le fait qu’un code spécifique s’exécute lorsque l’on clique un bouton, lorsque l’on survol un champ, lorsque l’on sélectionne une ligne dans une table…

La programmation événementielle est donc complémentaire aux autres types de programmations. Car une fois que la procédure s’exécute il peut s’agir de lignes de codes « directes », d’une procédure locale, d’une procédure globale, ou d’une méthode…

Programmation 'à la ligne'

La première façon de programmer est d’écrire des lignes de programmation, les une à la suite des autres sans architecture.
Cette méthode à l’avantage de la simplicité, mais pour inconvénients la maintenance et la réutilisabilité.
Maintenance :
– En cas d’erreur dans le code il n’est pas toujours facile de localiser un problème sur un code de plusieurs centaines de lignes.
– Si certaines parties de code se répètent, lors d’une correction il faudra bien reporter sur toutes les autres parties similaires
Réutilisation :
– Si un traitement similaire doit être fait dans la même application ou dans une autre application, le code devra être réécrit et les corrections/amélioration reportées

Exemple :

Recherche des factures d’un contenant le produit n°578 et contenant un produit gratuit

Dans cet exemple, si ce traitement comporte une erreur, ou si le code produit recherché vient à changé, il faudra vérifier partout dans le code de l’application et bien reportés les modifications.

bLigneGratuite est un booléen
bProd78 est un booléen
 
HLitPremier(Facture,IdFacture)
TANTQUE PAS HEnDehors(Facture)
   bProd78=Faux
   bLigneGratuite=Faux
   //Détail des lignes de la facture
   HLitRecherche(LigneFacture,IdFacture, Facture.IdFacture)
   TANTQUE HTrouve(LigneFacture) ET PAS (bProd78 ET bLigneGratuite)
      SI LigneFacture.IdProduit=578 ALORS
         //Produit trouvé
         bProd78=Vrai
      FIN
      SI LigneFacture.PrixProduit=0 ALORS
         //Ligne gratuite trouvé
         bLigneGratuite=Vrai
      FIN
   FIN
 
   //Facture Ok ?
   SI (bProd78 ET bLigneGratuite) ALORS
      //Facture Ok
      TableAjoute(TableRes,Facture.IdFacture)
   FIN
 
   HLitSuivant(Facture,IdFacture)
 
FIN

Programmation procédurale
La première façon d’organiser le code est de réaliser des procédures. Cela permet de rendre les codes plus lisibles, et les traitements plus réutilisables.
Si le traitement est spécifique à une fenêtre, et qu’il ne sera pas utilisé ailleurs que dans la fenêtre (car il manipule des champs de la fenêtre par exemple), il faut faire des procédures locales.
Si le traitement est un peu plus général, il faut faire des procédures globales. Si des valeurs de champs de la fenêtre sont nécessaires, il faut passer les valeurs en paramètres aux procédures.

Dans l’exemple ci-dessus on obtient des procédures plus simples, plus claires. Même si dans ce cas simple le gain n’est pas très important.

Exemple :

//Recherche des factures d'un contenant le produit n°578 et contenant un produit gratuit
HLitPremier(Facture,IdFacture)
TANTQUE PAS HEnDehors(Facture)
   //Facture Ok ?
   SI FactureContientProduitNuméro(Facture.IdFacture,578) _ET_ FactureContientProduitGratuit(Facture.IdFacture) ALORS
      //Facture Ok
      TableAjoute(TableRes,Facture.IdFacture)
   FIN
   HLitSuivant(Facture,IdFacture)
FIN
 
//Avec les procédures suivantes :
PROCEDURE FactureContientProduitNuméro(NuméroFacture,IdentifiantProduitRecherché)
//Détail des lignes de la facture
HLitRecherche(LigneFacture,IdFacture, NuméroFacture)
TANTQUE HTrouve(LigneFacture)
   SI LigneFacture.IdProduit= IdentifiantProduitRecherché ALORS
      //Produit trouvé
      RENVOYER Vrai
   FIN
FIN
//Produit pas trouvé
RENVOYER Faux

Programmation procédurale avec les collections de procédures

Il s’agit de la même chose que la notion de programmation procédurale.

La notion de collections de procédures que l’on trouve dans WinDev, et une notion qui permet de classer les procédures que l’on utilise par famille, ou par interaction.

Si une procédure utilise une autre procédure, il est préférable de les regrouper dans la même collection de procédures. Cela permet de mieux retrouver les procédures que l’on cherche, et de mieux réutiliser les procédures d’un projet à un autre.

Programmation orientée objet

Certains développeurs aiment dire qu’ils développent en objet pour dirent qu’ils développent mieux que les autres. Ce n’est pas forcément vrai. En programmation objet comme dans tout autre méthode de programmation, on peut bien ou mal développer.

Programmer en objet n’est pas plus difficile que les autres types de programmation. Il y a certaines règles à connaître, et surtout une syntaxe spécifique (syntaxe qui dépend du langage).

La programmation objet est très proche de la programmation procédurales avec la notion de collection de procédures proposée par PC SOFT.
Note : Dans un premier temps il est fait abstraction des notions avancées de la POO (héritage, surcharge, dérivation….)

La principale différence est que pour utiliser les méthodes d’une classe il faut au préalable déclarer un objet (une variable) basé sur la classe (le type). Pourquoi cette contrainte ? Cela permet d’utiliser en même temps plusieurs procédures qui manipulent des variables globales sans qu’elles n’interagissent les unes avec les autres.

Un cas concret :
Une procédure qui fait une trace, et qui en fonction d’une variable globale effectue :
– la trace à l’écran,
– dans un fichier
– ne fait rien.
Si on utilise une procédure issue d’une collection, il ne sera pas possible d’utiliser la même fonction pour dans certains cas faire une trace à l’écran, dans d’autre une trace dans un fichier.
La fonction TraceInfo() fera toujours la même chose (qui dépend de la variable globale)

Si on utilise une classe, il sera possible d’instancier 2 objets, qui auront donc chacun leurs variables (membres) avec des valeurs indépendantes.

Exemple :

//Déclaration d'un objet basé sur la classe clTrace
//Objet qui initialise une variable de la classe (membre) à "Ecran"
ObjTraceEcran est un objet clTrace("Ecran")
//Déclaration d'un 2ième objet basé sur la classe clTrace
//Objet qui initialise une variable de la classe (membre) à "Fichier"
ObjTraceFichier est un objet clTrace("Fichier")
 
//Utilisation de l'objet pour effectuer une trace écran
ObjTraceEcran:TraceInfo("Ajout d'une trace à l'écran")
//Utilisation de l'objet pour effectuer une trace fichier
ObjTraceFichier:TraceInfo("Ajout d'une trace dans un fichier")

Démystification ou la POO pour les nuls

Certains développeurs aiment dire qu’ils développent en objet pour dirent qu’ils développent mieux que les autres. Ce n’est pas forcément vrai. En programmation objet comme dans tout autre méthode de programmation, on peut bien ou mal développer.

Programmer en objet n’est pas plus difficile que les autres types de programmation. Il y a certaines règles à connaître, et surtout une syntaxe spécifique (syntaxe qui dépend du langage).

La programmation objet est très proche de la programmation procédurales avec la notion de collection de procédures proposée par PC SOFT.
Note : Dans un premier temps il est fait abstraction des notions avancées de la POO (héritage, surcharge, dérivation….)

La principale différence est que pour utiliser les méthodes d’une classe il faut au préalable déclarer un objet (une variable) basé sur la classe (le type). Pourquoi cette contrainte ? Cela permet d’utiliser en même temps plusieurs procédures qui manipulent des variables globales sans qu’elles n’interagissent les unes avec les autres.

Exemple avancé reprenant les différentes syntaxes à utiliser en programmation objet en W-Langage.

Attention, les exemples sont anciens et la syntaxe a été allégée. Plus besoin d’un : devant les noms et le : entre classe et méthode peut être remplacé par un point.

//*********** Exemple de code de déclaration d'une classe en W-Langage
//Constantes de la classe
//Remarque : on est avant "ClasseExemple est une classe"
CONSTANT
//Syntaxe générale : =Valeur
CST1CLASSEEX= 10
CST2CLASSEEX= "BONJOUR"
FIN
 
//Structure utilisée par la classe
//Remarque : on est avant "ClasseExemple est une classe"
StructurePourClasse est une structure
MembreStructureClasse est un entier
//Autres membres de la structure
FIN
 
//Membres de la classe (ClasseExemple représente le nom de la classe)
ClasseExemple est une classe
//Uniquement  dans le cas où la classe hérite d'une autre classe
//Syntaxe générale : hérite de 
//A mettre impérativement au début avant les membres de la classe
hérite de ClasseAncêtre
 
//On peut noter qu'il est également possible d'utiliser cette autre syntaxe :
//un objet ClasseAncêtre(ParamConstructeurAncêtre)
//Je conseille toutefois la première syntaxe
 
//Membres globaux (commun à tous les objets bases sur cette classe dans le projet)
GLOBAL
MembreGlobal est une chaîne
 
//Membres locaux (propre à chaque objet, à chaque instanciation de la classe) : Par défaut
LOCAL
//Membres privés (utilisables uniquement dans la classe)
PRIVÉ
//Uniquement  dans le cas où la classe utilise une autre classe
MembreObjet est un objet AutreClasse
//Membres privés de la classe
MembreClasse est une chaîne
 
//Membres publics (utilisables dans la classe et en dehors de la classe) : Par défaut
PUBLIC
MembreClasseBaséSurStructure est un StructurePourClasse
 
//Membres publics constant (utilisables en L/E dans la classe et en lecture seule en dehors de la classe)
PUBLIC CONSTANT
MembreConstantClasse est un StructurePourClasse
 
//Membres protégés (accès autorisé depuis un code de la classe ou un code d'une classe dérivée)
PROTÉGÉ
MembreProtege est un entier
FIN
 
 
//**************** Exemple de constructeur de classe
//ici constructeur qui attend 2 paramètres
PROCEDURE Constructeur(Param1,Param2)
//appel du constructeur de la classe ancêtre (si nécessaire)
//A mettre impérativement au début avant les autres initialisations
Ancêtre:Constructeur(Param1)
//OU
//Constructeur Ancêtre(Param1)
//OU
//En cas d'héritage multiple, ou pou être plus explicite on peut nommer la classe ancêtre :
//ClasseAncêtre:Constructeur(Param1)
//OU
//Constructeur ClasseAncêtre(Param1)
 
//En cas d'héritage multiple, ou pou être plus explicite on peut nommer la classe ancêtre :
//ClasseAncêtre: Constructeur(Param1)
 
//appel des constructeurs des membres qui sont eux-mêmes des objets (si nécessaire)
:MembreObjet:Constructeur(Param2)
 
//initialisation des membres (si nécessaire)
//A part dans la déclaration de la classe, pour manipuler les membres de la classe, il faut les préfixer de ":" (de la même façon pour les méthodes)
:MembreClasse = "INIT"
//Ici affectation d'un membre basé sur une structure
:MembreClasseBaséSurStructure:MembreStructureClasse = 10
 
 
 
//**************** Exemple de méthode de classe (déclaration et manipulation des différents éléments de la classe dans le code)
PROCEDURE PROTÉGÉ VIRTUELLE MéthodeExemple(Parametre1,Parametre2="")
//PROTEGE: car la méthode n'est accessible que depuis les autres méthodes de la classe et les classes dérivées.
//Il faut mettre PRIVE pour pouvoir appeler cette méthode que depuis la classe elle-même. Et il ne faut rien mettre (ou PUBLIC) pou pourvoir l'appeler en dehors de la classe
//Il est possible de mettre GLOBAL (entre PROTEGE et VIRTUELLE) pour indiquer que cette méthode peut être appelée sans instanciation d'objet (juste avec la classe)
//VIRTUELLE indique que si cette méthode est redéfinie dans une classe dérivée, c'est la méthode de la classe dérivée qui sera appelée même depuis cette classe
 
//** Ici exemple d'appel de la méthode de même nom de la classe ancêtre (méthode qui existe donc dans la classe en cours est dans la classe ancêtre)
//Il faut ajouter le mot "Ancêtre" (ou le nom de la classe en cas d'héritage multiple) devant le nom de la méthode
Ancêtre:MéthodeExemple(Parametre1)
// et donc l'autre possibilité en cas d'héritage multiple : 
//ClasseAncêtre:MéthodeExemple(Parametre1)
 
//Exemple d'utilisation d'un membre global : il faut préfixer de "::" ( de la même façon pour une méthode globale)
::MembreGlobal=10
//Exemple d'utilisation d'une constante de la classe : il faut préfixer de "::" ( les constantes sont des membres globaux non modifiables)
Info(::CST1CLASSEEX)
 
 
//**************** Exemple de code (hors de la classe elle-même), qui utilise une classe
//Déclaration de l'objet (ici on passe 2 paramètres au constructeur)
ObjPerso est un objet ClasseExemple(1,2)
// si le constructeur n'a pas de paramètre, il faut écrire :
//ObjPerso est un objet ClasseExemple
 
//Utilisation d'une structure de la classe
MaStructure  est un ClasseExemple::StructurePourClasse
 
//Appel d'une méthode
ObjPerso:MéthodePublic("Mon Paramètre")
 
//Appel d'une méthode globale : il faut utiliser le nom de la classe (et pas celui d'un objet), et il faut préfixer le nom de la méthode de "::"
ClasseExemple::MéthodeGlobale("Autre Paramètre")
 
//Appel d'une constante de la classe : il faut utiliser le nom de la classe (et pas celui d'un objet), et il faut préfixer le nom de la constante de "::"
Info(ClasseExemple::CST1CLASSEEX)

Exemple de classe utilitaire

Voici une première classe utilitaire.
Elle permet de modifier le comportement des champs séparateurs (splitters).

Détail :
Lorsqu’il y a plusieurs champs séparateur dans une même fenêtre, en exécution ceux-ci peuvent se « croiser » et donner des affichages non prévus. Lorsqu’ils ne se croisent pas. De la même façon pour déplacer un séparateur compris entre deux autres séparateur, il est nécessaires de modifier au préalable l’emplacement du séparateur se trouvant au dessus ou au dessous.

Télécharger la classe

Et une fenêtre de test qui utilise cette classe.
Fenêtre avec 3 séparateurs horizontaux et 3 séparateurs verticaux.
Vous pourrez voir que les déplacements des différents séparateurs « poussent » les autres séparateurs.

Fenêtre de test de la classe