• 1

Poo : Contrôleur de type

Note utilisateur:  / 3
MauvaisTrès bien 

Création d'un contrôleur de type d’objets en WLangage

Diagnostique

Le WLangage permet une programmation orientée objet. Toutefois celle-ci s’avère plutôt alambiquée quant au contrôle des types.

En effet la transformation des types est très permissive comme le montre l’exemple suivant :

Soit deux classes, ClasseA et ClasseB.

ClasseA dispose d’une méthode UneMethodeA()

ClasseB dispose d’une méthode UneMethodeB().

Le code suivant, pourtant incohérent, ne génèrera ni erreur de compilation ni erreur à l’exécution :

//---------------------------
gpo_MonObjet est un ClasseA dynamique
gpo_MonObjet <- allouer un ClasseB()
//---------------------------

L’utilisation d’un objet de type B en tant qu’objet de type A déclenchera une erreur… lors de son exploitation (invocation de la méthode de A dont B ne dispose pas), ce qui peut être « trop tard ». Au plus tôt une incohérence est signalée au cours de l’exécution, au mieux c’est !

//---------------------------
gpo_MonObjet:UneMethodeA() : Déclenche une erreur d’exécution
//---------------------------

Les adeptes du typage fort seront outrés : l’instruction de prise de référence a été autorisée quitte à laisser la variable gpo_MonObjet non allouée…

Cela s’explique : WinDev, afin d’honorer sa promesse de nous faire développer 10 x plus vite, utilise pour cela une souplesse de programmation : un mécanisme de transtypage très élaboré. Le typage se résout de lui-même sans provoquer d’erreur de compilation ni d’exécution (et donc au détriment de la sécurité – on ne peut pas tout avoir).

Le revers de la médaille est que l’on aboutit à des bugs parfois difficiles à traquer… car leur origine provient d’un transtypage implicite. Passer par exemple d’une chaine à un numérique pour revenir à une chaine provoque potentiellement une modification de la donnée d’origine.

Solution

La solution générale consiste à un contrôle établi par le développeur lui-même. Nous allons voir par la suite comment contrôler le type des objets… et d’une manière générale on peut appliquer ce même contrôle aux type de bases. L’exemple type est le prototypage des fonctions et procédures :

//---------------------------
PROCEDURE UneProcedure(UnParametre1, UnParametre2) // ici on ne sait pas (y compris le compilateur) ce qui est attendu, ce qui sera renvoyé...

PROCEDURE UneProcedure(UnParametre1 est une chaîne, UnParametre2 est un entier) : booléen // ici les types attendus et renvoyés sont très clairs, le compilateur ne manquera pas de vous signaler une incohérence !
//---------------------------

N’hésitez pas à déclarer explicitement les types. Pour 5 secondes de « perdues » à l’écriture par flemme vous gagnerez de nombreuses minutes à éviter un débogage pour comprendre pourquoi vos variables sont transformées sans votre consentement au cours de l’exécution.

Egalement, le compilateur est là pour vous aider et assure alors que le type transmis est le même que celui reçu. En outre l’exécution est accélérée car elle n’utilise plus le mécanisme de transtypage qui a forcément un coût CPU. Le WLangage permet d’utiliser des outils de réflexivité qui peuvent nous aider à contrôler les types lorsqu’on le souhaite.

Une propriété très « classe »…

Voyons maintenant comment contrôler le type des objets.

La propriété « ..Classe » est disponible pour les objets. Elle retourne une chaine de caractères contenant le nom de la classe correspondant à l’objet.

//---------------------------
go_MonObjetA est un ClasseA
go_MonObjetB est un ClasseB
Info(go_MonObjetA..Classe) // affichera "ClasseA"
gb_Compatible est un booléen = (go_MonObjetA..Classe = go_MonObjetB..Classe) // un moyen de comparer si les types sont compatibles ("ClasseA" = "ClasseB" renverra donc faux)
//---------------------------

On peut donc déterminer de cette manière l’équivalence de type entre deux objets.

… qui ne suffit à LISKOV…

Nous déterminons l’équivalence de type de deux objets. Oui mais… et le polymorphisme ?

Supposons une ClasseC héritant de ClasseA, la comparaison par la propriété « ..Classe » nous renverra systématiquement faux.

Alors que le principe de LISKOV nous garantit qu’une instance de ClasseC doit pouvoir se substituer à une instance de ClasseA (compatibilité ‘descendante’, un objet de type ClasseC est reconnu comme étant également de type ClasseA) :

//---------------------------
gpo_MonObjetA est un ClasseA dynamique
gpo_MonObjetA <- allouer un ClasseC() // substitution de LISKOV
gpo_MonObjetA:UneMethodeA() // ceci est parfaitement autorisé et même reconnu par le compilateur !
//---------------------------

… sauf à lui ajouter l’introspection.

Pour résoudre ce problème, le type de variable « Définition » et la fonction RécupèreDéfinition() sont tout ce dont nous auront besoin.

Le type de variable « Définition » contient une propriété intéressante, il s’agit de « ..DéfinitionBase » qui est un tableau des Définitions des types dont la classe hérite.

//---------------------------
go_MonObjetC est un ClasseC
gadv_Def est une Définition = RécupèreDéfinition(go_MonObjetC) // la définition d'un objet Classe
gadv_DefBase est une Définition = gadv_Def..DéfinitionBase[1] // ClasseC hérite seulement de ClasseA, donc le tableau contient un et un seul élément
Info(gadv_DefBase..Nom) // affichera "ClasseA"
//---------------------------

En cas d’héritage multiple, « ..DéfinitionBase » contiendra autant de définitions qu’il y a d’héritages… ces définitions contenant leur propres « ..DéfinitionBase » si il y a héritage en cascade.

Il devient alors possible de réaliser une procédure de contrôle récursive permettant de déterminer si un objet est du même type qu’un autre… et donc de résoudre le contrôle de type des objets.

Exemple d’implémentation

Voici une possibilité d’implémentation d’un contrôleur de type :

//---------------------------
CL_TypeControleur est une Classe
	// Objet permettant le contrôle de typage
	PRIVÉ
		ms_ObjBaseType est une chaîne	
FIN
//---------------------------
PROCEDURE Constructeur(lpo_TypeRef est un objet dynamique = Null)
// Objet de référence
// L'objet doit être instancié pour que la récupération de type soit possible !!!
SI lpo_TypeRef <> Null ALORS
	// Mémorisation du type d'objet de base...
	:ms_ObjBaseType = lpo_TypeRef..Classe
FIN
//---------------------------
// Résumé : Contrôle si l'objet fourni est du type de base ou d'un type dérivé
// Syntaxe :
//[  = ] EstType ( est objet dynamique)
//
// Paramètres :
//	lpo_Ref (objet dynamique) : IN Objet à contrôler
// Valeur de retour :
// 	booléen : L'objet est du type de base ou d'un type dérivé
//
// Exemple :
// Indiquez ici un exemple d'utilisation.
//
PROCEDURE EstType(lpo_Ref est un objet dynamique) : booléen

l_Def est une Définition = RécupèreDéfinition(lpo_Ref)
SI l_Def..Nom = :ms_ObjBaseType _OU_ :EstTypeDerive(l_Def) ALORS RENVOYER Vrai

RENVOYER Faux

//---------------------------
// Résumé : Fonction récursive indiquant si la définition est un type dérivé
// Syntaxe :
//[  = ] EstTypeDerive ( est Définition)
//
// Paramètres :
//	l_Def (Définition) : IN Définition à vérifier
// Valeur de retour :
// 	booléen : La définition est un type dérivé
//
// Exemple :
// Indiquez ici un exemple d'utilisation.
//
PROCEDURE PRIVÉE EstTypeDerive(l_Def est une Définition) : booléen
// cas de base : pas de définiton de base, on sort
SI TableauOccurrence(l_Def..DéfinitionBase) = 0 ALORS RENVOYER Faux
l_DefBase est une Définition
POUR i = 1 _A_ TableauOccurrence(l_Def..DéfinitionBase)
	l_DefBase = l_Def..DéfinitionBase[i]
	SI l_DefBase..Nom = :ms_ObjBaseType ALORS RENVOYER Vrai // si mon type de base est dérivé => bingo !
	SI :EstTypeDerive(l_DefBase) ALORS RENVOYER Vrai // sinon vérification par appel récursif de types de base de mon type de base.
FIN
// Si l'on est sorti de la boucle, c'est un échec pour la définition courante
RENVOYER Faux
//---------------------------
// Résumé : Renvoie le type de base associé à ce contrôleur de type
// Syntaxe :
//[  = ] GetBaseType ()
//
// Paramètres :
//	Aucun
// Valeur de retour :
// 	chaîne ANSI : // 	Type de base associé à ce contrôleur
//
// Exemple :
// Indiquez ici un exemple d'utilisation.
//
PROCEDURE GetBaseType() : chaîne
RENVOYER :ms_ObjBaseType
//---------------------------
Exemple d’utilisation de notre Contrôleur de type : 
//---------------------------
go_MonObjetA est un ClasseA()
go_MonObjetB est un ClasseB()
go_MonObjetC est un ClasseC()

go_TypeCtrl est un CL_TypeControleur(go_MonObjetA)

Info(go_TypeCtrl:GetBaseType()) // affichera "ClasseA"
go_TypeCtrl:EstType(go_MonObjetB) // renverra faux – car ClasseB n’est pas de type ClasseA
go_TypeCtrl:EstType(go_MonObjetC) // renverra vrai – car ClasseC est dérivé de ClasseA

//---------------------------

Et voilà, un contrôleur de type en WLangage prenant en compte le polymorphisme !

Bon dev à tous !

 En discuter...

#896266D

image image