2. Encore plus de réutilisation et d’abstraction
2.1 Un outil logiciel réutilisable : le composant liste visuelle
2.2 Un degré plus haut : une liste visuelle générique
Rappelons que le TAD liste linéaire de chaînes a déjà été étudié et implanté en pascal.
TAD Liste
utilise : N{entiers}, T0=chaîne de caractères , Place
Champs : (a1,.....,an) suite finie dans T0
opérations :
liste_vide : ® Liste
acces : Liste x N ® Place
contenu : Place ® T0
kème : Liste x N ® T0
long : Liste ® N
supprimer : Liste x N ® Liste
inserer : Liste x N x T0® Liste
succ : Place ® Placepréconditions :
acces(L,k) def_ssi 1 £ k £ long(L)
supprimer(L,k) def_ssi 1 £ k £ long(L)
inserer(L,k,e) def_ssi 1 £ k £ long(L)+1
kème(L,k) def_ssi 1 £ k £ long(L)
Fin-Liste
1. Quelques méthodes d’implantation de ce TAD
Nous allons utiliser les possibilités de l’outil Delphi pour implanter ce genre de structure de données. Nous classerons nos implantations selon plusieurs niveaux d’abstraction. Ce qui nous permettra sur cet exemple de comparer les différents efforts consacrés à ces implantations et leurs résultats eu égard à la réutilisation et à la protection. Nous nous poserons à chaque fois la question suivante : " comment pouvons-nous nous servir du code développé dans un autre logiciel ? ".
Pascal.Unit_listlin\Plistchn.pas
Nous ne nous attarderons pas sur ce niveau qui est le plus bas ; il suffit de dire qu’à ce niveau l’on a construit un programme pascal P avec des procédures et des fonctions.
Effort d’implantation :
Cet effort est maximal puisque nous avons choisi de simuler la liste dans un tableau. Par exemple pour la procédure supprimer, nous pourrions avoir le code source suivant :
procedure supprimer (var L: liste; rang: integer);
var
n: 0..max_elt;
i: 1..max_elt;
begin
if not Est_vide(L) then
begin
n := longueur(L);
if rang in [1..n] then {le rang est correct}
begin
if (n=1)or(rang=n) then
L.long :=n-1 {un seul élément ou le dernier}
else
begin {n>1 et rang < n}
for i := rang to n - 1 do
L.suite[i] := L.suite[i + 1];
L.long := n - 1
end
end
end
end;{supprimer}Réutilisation :
La réutilisation de cet effort est pratiquement inexistante. Nous pourrons uniquement recopier manuellement les types, les fonctions et les procédures de P dans le code source d’un autre logiciel.
Protection :
Le niveau de protection du code contre des erreurs de programmation est relativement faible. La seule protection que nous ayons est apportée par la visibilité du code en pascal (local, global, paramètres formels et effectifs).
Pascal.Unit_listlin\Ulistchn.pas & Delphi.ListeChaines \Liste0.Dlfi
C’est ainsi que nous avons précédemment implanté ce TAD par traduction du TAD en la partie interface d’une unité :
Unit UListchn;
interface
const
max_elt = 1000;
type
T0 = string;
liste = record
suite: array[1..max_elt] of T0;
long: 0..max_elt;
init_ok:char;
end;function Est_vide(L:liste):boolean;
function longueur (L: liste): integer;
procedure supprimer (var L: liste; rang: integer);
procedure inserer (var L: liste; rang: integer; x: T0);
function kieme (L: liste; rang: integer): T0;
procedure ajouter(var L:liste;x:T0);
procedure effacer(var L:liste);
function Test (L: liste; x: T0): boolean;
procedure Rechercher (L: liste; x: T0; var place: integer);
procedure init_liste(var L:liste);implementation ...
Effort d’implantation :
Cet effort est identique au précédent car là aussi nous avons choisi de simuler la liste dans un tableau (par exemple le code source de l’opération supprimer reste identique à celui du paragraphe précédent).
Réutilisation :
La réutilisation existe et se fait par utilisation du code source à travers la clause uses de l’unité " Unit Ulistchn " dans le logiciel à développer.
Protection :
La protection est améliorée. Elle cumule le niveau précédent au masquage du code source dans la partie " implementation " (partie privée de l’unité).Il reste un problème lié au fait qu’il est impossible d’empêcher des effets de bords entre plusieurs unités. Nous avons observé que certains étudiants pratiquaient par étourderie dans des unités, des redéfinitions de variables ou de types. Le compilateur n’a pas pu déceler l’erreur et a interprété cette erreur comme un masquage d’identificateur.
Remarquons qu’avec des langages comme Ada ou Java ce type d’erreur a été corrigé et n’est pas possible.
1.3 Niveau classe à partir d’une unité existante
Delphi.ListeChaines \Liste1Class.Dlfi
Nous reprenons l’unité précédente et nous pratiquons une réorganisation du code sous forme d’une classe Delphi.
const
max_elt = 1000;
type
T0 = string;
liste =class
public
function Est_vide:boolean;
function longueur : integer;
procedure supprimer (rang: integer);
procedure inserer (rang: integer; x: T0);
function kieme (rang: integer): T0;
procedure ajouter(x:T0);
procedure effacer;
function Test (x: T0): boolean;
procedure Rechercher (x: T0; var place: integer);
procedure init_liste;
procedure creationListe(Box:TListBox);
private
suite: array[1..max_elt] of T0;
long: 0..max_elt;
init_ok:char;
procedure Test_Recherche(x:T0;var trouve:boolean;
var rang:integer);
end;Effort d’implantation :
Cet effort est identique au précédent car là encore nous avons choisi de simuler la liste dans un tableau. Soit à titre d’exemple le code source de l’opération supprimer :
procedure liste.supprimer (rang: integer);
var
n: 0..max_elt;
i: 1..max_elt;
begin
if not self.Est_vide then
begin
n := self.longueur;
if rang in [1..n] then {le rang est correct}
begin
if (n=1)or(rang=n) then
self.long :=n-1 {un seul élément ou le dernier}
else
begin{n>1 et rang < n}
for i := rang to n - 1 do
self.suite[i] := self.suite[i + 1];
self.long := n - 1
end
end
end
end;{supprimer}Réutilisation :
La réutilisation est meilleure car au lieu d’utiliser du code source à travers la clause uses de l’unité " Unit Ulistchn ", nous pouvons nous servir de la classe " liste " comme d’un type pascal dans le logiciel à développer (var x : liste ; ...).
Protection :
La protection est accrue. Elle cumule ici aussi le niveau précédent avec le concept d’encapsulation de l’information dans la classe elle-même. L’étourderie signalée précédemment n’existe plus puisque des champs de deux classes C1 et C2 peuvent avoir le même nom (le principe de la référence uniforme C1.Monchamp et C2.Monchamp différencie les deux données Monchamp de chacune des deux classes C1 et C2.).
Nous allons maintenant utiliser pour nos listes de chaînes une autre démarche d’implantation. Nous cherchons s’il existe dans Delphi une classe qui effectue déjà ces actions sur les chaînes. La classe des TStringList qui est une classe interne importante de Delphi correspond en fait à notre attente.
1.4 Niveau classe non visuelle avec héritage : TStringList
Delphi.ListeChaines \Liste2ObjBase.Dlfi
Reprenons un résumé de la documentation Borland sur cette classe.
Un objet TStringList gère
une liste de chaînes.
Les principales méthodes
du TStringList :
Les
attributs en Delphi se dénomment des propriétés.
property Count
property Strings |
TStringList.Count
La propriété Count contient le nombre de chaînes dans la liste. property Count: Integer; |
TStringList.Strings
La propriété Strings permet d'accéder(lire ou modifier) à une chaîne particulière à partir de sa position (indice) dans la liste, la première chaîne étant référencée par l’indice 0. property Strings[Index: Integer]: string; |
Exemple :
MonStringList.Strings[0] := 'la première chaîne';
MonStringList.Strings[1] := 'la deuxième chaîne';
Un TStringList est sensible à deux événements :
L’objet réagit aux deux événements OnChange et OnChanging dont on rappelle qu’en Delphi ce sont des propriétés (des attributs) :
property OnChange: TNotifyEvent;
property OnChanging: TNotifyEvent;
TStringList.OnChange et TStringList.OnChanging
OnChange se produit immédiatement après une modification de la liste de chaînes. OnChanging se produit juste avant une modification de la liste de chaînes.
Dès lors que des chaînes de la liste sont ajoutées, supprimées, déplacées ou modifiées, trois événements ont lieu dans l’ordre suivant :
1° L’événement OnChanging se produit avant les modifications. 2° Les chaînes sont ajoutées, supprimées, déplacées ou modifiées.
3° L’événement OnChange se produit ensuite.
Notre classe de liste héritant du TStringList :
type
T0 = string;
liste =class(TStringList) //héritage de la classe TStringList
public;
function longueur : integer;
procedure supprimer (k: integer);
procedure inserer (k: integer; x: T0);
function kieme (n: integer): T0;
function Test (x: T0): boolean;
procedure Rechercher (x: T0; var place: integer);
procedure Ajouter(x:T0);
procedure effacer;
private
function Est_vide:boolean;
end;Effort d’implantation :
Cet effort est très faible car nous héritons des propriétés et des méthodes de la classe TStringList. Il nous suffit d’encapsuler les méthodes de TStringList pour construire nos propres méthodes. Soit par exemple le code source de supprimer, inserer, kieme :
implementation procedure liste.supprimer (k: integer);
begin
self.Delete(k-1); //méthode de l’ancêtre
end;procedure liste.inserer (k: integer; x: T0);
begin
self.Insert(k-1,x); //méthode de l’ancêtre
end;function liste.kieme (n: integer): T0;
begin
kieme:=self.strings[n-1] //méthode de l’ancêtre
end;Le constat est clair :écrire ce code a demandé peu de temps, car ici nous encapsulons des méthodes existantes !
Réutilisation :
La réutilisation est la même que pour le niveau précédent. Nous pouvons nous servir de la classe " liste " comme d’un type pascal dans le logiciel à développer (var x : liste ; ...).
Protection :
La protection est très améliorée par rapport au niveau précédent. En effet la classe ancêtre TStringList possède des protections intrinsèques dont nous avons hérité sans avoir à les développer nous mêmes.
Nous sommes ici au sommet de ce que nous pouvons espérer, dans ce cas et avec cet outil à tous égards.
Amélioration possible quant au chargement des données :
Telle qu’elle est définie dans le paragraphe précédent la classe liste bénéficie de la méthode de chargement des chaînes LoadFromFile de son ancêtre.
Etant en environnement visuel, il serait dommage de ne pas en profiter ; c’est pourquoi nous ajoutons dans la partie " public " de la classe pour l’exemple, une nouvelle méthode de chargement CreationListe : elle permet de recopier le contenu des données d’un objet de la classe visuelle des TListBox dans notre liste.
Voici le code correspondant à cette méthode :
liste =class(TStringList) //héritage de la classe TStringList
public;
function longueur : integer;
procedure creationListe(Box:TListBox);
....... le reste identiqueimplementation
.....
procedure liste.creationListe(Box:TListBox);
{création de la liste (le ListBox est déjà chargé manuellement}
begin
self.AddStrings(Box.Items);
end;
Vous trouverez dans le dossier ci-dessous, un exemple d’utilisation de la classe liste basée sur le TstringList.
Delphi.ListeChaines \Liste3PiLifo.Dlfi
Il s’agit de l’implantation du TAD pile LIFO fondé sur l’implantation d’une liste. Rappel du TAD Pile LIFO :
TAD: PiLifo Utilise:
{Vrai,Faux}
T0 = chaînes de caractères
Opérations:
fond : à PiLifo
Est_Vide : PiLifo à {Vrai,Faux}
Empiler : PiLifo x T0 à PiLifo
Depiler : PiLifo à PiLifo x T0
premier : PiLifo à T0
Préconditions:
Depiler ( P ) def_ssi Est_vide ( P ) = Faux
premier ( P ) def_ssi Est_vide ( P ) = Faux
FinTAD_PilifoVoici la classe PiLifo dérivée de la classe liste précédente :
type
T0 = string;
piLifo =class(liste)
public
function Est_Vide_Pile: boolean;
procedure Empiler (elt: T0);
procedure Depiler (var elt: T0);
function premier: T0;
procedure creationPiLifo(Box:TListBox);
end;
Nous savons qu’il est toujours ennuyeux de devoir écrire des lignes de code uniquement afin de " voir " ce qui se passe à l’intérieur de notre structure de donnée. C’est à la fois un besoin pédagogique (observer et scruter pas à pas le fonctionnement de la liste et des actions qui y ont lieu) et une nécessité de test. Dans le cas des tests, le code supplémentaire sert à visualiser les états des données en mode mise au point pour des programmes utilisant la classe liste.
Comme la classe visuelle des
TlistBox dispose d’un champ items qui hérite des propriétés
des TStringList (en fait TStrings semblable aux TStringList), nous allons
utiliser ce champ comme base de notre liste visuelle, le reste du TlistBox
nous servira aux nécessités pédagogiques et de test.
type
T0 = string; liste =class(TListBox) {héritage de la classe TListBox } public function longueur : integer; procedure supprimer (k: integer); procedure inserer (k: integer; x: T0); function kieme (n: integer): T0; function test (x: T0): boolean; procedure rechercher (x: T0; var place: integer); procedure ajouter(x:T0); procedure effacer; private function est_vide:boolean; end; implementation
procedure liste.inserer
(k: integer; x: T0); function liste.kieme
(n: integer): T0; |
Cette approche par dérivation
d’un objet visuel du RAD s’est révélée à l’usage
être un bon compromis pour une utilisation pédagogique de l’outil
visuel en initiation. Encore une fois l’aspect visuel renforce la compréhension
du mécanisme de dérivation d’une classe à partir d’une
autre classe.
Effort d’implantation :
L’effort est équivalent
à celui produit dans le cas de la classe non visuelle héritant
du TStringList. Une amélioration est à noter quant à
l’effort de conception qui est facilité par le support visuel du composant
TListBox.
Protection :
La protection est très améliorée
par rapport à la classe non visuelle héritant du TStringList.
En effet nous héritons d’encore plus de protection fournie par le TListBox
relativement à la gestion visuelle des données.
2. Encore plus de réutilisation et d’abstraction
Nous construisons dans ce paragraphe
un composant réutilisable au même niveau que les objets visuels
de base du RAD. La méthode générale et pratique de
développement de ce genre de composant n'est pas donnée ici,
mais se trouve au chapitre 7 consacré aux outils logiciels. Nous utilisons
la facilité d’extensibilité de l’environnement de développement
Delphi (rappelons que l’extensibilité est l’un des facteurs de qualité
d’un logiciel). D’ailleurs une quantité importante de tels composants
est proposée par des particuliers ou des professionnels en démonstration,
en shareware ou en freeware sur le web.
2.1 Un outil logiciel réutilisable : le composant liste visuelle
Nous reprenons strictement le
niveau classe visuelle avec héritage du TListBox, auquel il suffit
d’ajouter une méthode constructeur permettant de déposer le
futur composant visuel. En fait le constructeur de TListBox est create,
c’est celui de son ancêtre Tobject. Ce constructeur est surchargeable1 dans tous les descendants de Tobject.
Afin de pouvoir utiliser ses propriétés intrinsèques
dans le descendant le RAD Delphi dispose de la clause inherited. Cette
clause permet dans un descendant, d’hériter automatiquement des actions
d’une méthode ancêtre surchargée.
Voici
une partie du code correspondant au composant :
interface
type T0 = string; liste =class(TListBox) //héritage de la classe TListBox public constructor create(Aowner:Tcomponent);override; function longueur : integer; procedure supprimer (k: integer); procedure inserer (k: integer; x: T0); function kieme (n: integer): T0; function test (x: T0): boolean; procedure rechercher (x: T0; var place: integer); procedure ajouter(x:T0); procedure effacer; private function est_vide:boolean; end; procedure register;
implementation procedure register;
....code identique à la liste visuelle constructor liste.create(Aowner:Tcomponent);
|
Après ces opérations
nous disposons d’un nouveau composant visuel pour développer des
programmes utilisant des listes de chaînes.
Voici un aspect de la palette des composants après enregistrement
Effort d’implantation :
L’effort est légèrement
supérieur à celui produit dans le cas de la classe visuelle
héritant du TlistBox à cause du constructeur à définir.
Cet aspect d’uniformisation a
un impact pédagogique très puissant auprès des étudiants
qui les valorise dans leur activité de programmation. Ils ont ajouté
eux-mêmes un nouveau produit à un grand ensemble. Ils prennent
conscience de la notion d’extensibilité d’un logiciel et apprennent
à penser et à écrire des programmes pour que d’autres
les utilisent en mode conception. Ce qui n’est pas la même chose que
de construire des programmes qui ne sont utilisés qu’en mode exécution.
L’étudiant apprend à faire de la programmation défensive
car il veut que son composant soit robuste lors de son utilisation.
Une fois déposé
sur la fiche de conception, voilà l’aspect du composant liste visuelle
(même aspect que son parent le TListBox):
Nous terminons cet ensemble de remarques sur le TAD liste de chaînes en montrant qu’il est possible de parler de généricité à un débutant avec l’outil Delphi. Nous allons construire une liste visuelle qui s’adaptera à l’utilisateur (un programmeur) et dont les données pourront être soit des entiers, soit des réels, soit des chaînes, soit des booléens, soit des objets. Nous allons voir qu’il n’est pas très difficile de construire un objet polymorphe à partir du TAD de liste générale dans lequel le type T0 de données internes à la liste n’est pas défini à l’avance mais se trouve passé comme un " paramètre ".
Nous souhaitons que ce composant soit à la disposition du programmeur et qu’il puisse lors de la conception se servir de cette liste polymorphe. Nous souhaitons enfin qu’à partir de cette classe le programmeur puisse utiliser des objets instanciés de cette liste sous forme de liste d’entiers ou de liste de chaînes etc...
Nous montons donc plus haut dans l’abstraction ce qui est le propre de la généricité.
Nous pouvons simplement construire
notre composant parce que Delphi dispose déjà d’un objet générique,
le type Variant qui est très intéressant, mais dangereux s’il
est mal utilisé par un débutant1. Dans le cas présent nous verrouillons le type variant
en ne l’utilisant que pour ses propriétés de généricité
et seulement pour celles-ci, en gérant nous-mêmes le transtypage.
Nous avons le contrôle de toutes les opérations internes ce
qui nous assure le minimum de " dérapage " possible.
Remarque :
Le type variant possède
une fonction lui permettant d’indiquer quel est le type actuellement supporté
par le variant :
function VarType(x :Variant)TypedeVariant |
Voici
l’interface de la unit contenant le composant TlisteVisuGen.
unit UcompoListeVisugeneric;
interface
procedure register; implementation
|
après enregistrement dans l’onglet perso de la palette des composants.
Examinons chaque partie de la classe TlisteVisuGen
Le type d’élément
T0 est un variant.
la partie
public
Rien de particulier, elle est identique
à la partie public de la classe visuelle non générique.
procedure TlisteVisuGen.SetTypeObjet(x:ElementListe);
begin case x of entier :FTypeObjet:=varInteger; reel :FTypeObjet:=varCurrency; booleen :FTypeObjet:=varBoolean; chaine :FTypeObjet:=varString; objet :FTypeObjet:=varvariant; end end; |
Remarque
: varInteger,…,varvariant sont des constantes symboliques prédéfinies
de Delphi.
La function GetTypeObjet:ElementListe
sert à transtyper la variable FTypeObjet en le résultat
de type énuméré ElementListe associé.
function TlisteVisuGen.GetTypeObjet:ElementListe;
begin case FTypeObjet of varInteger :result:=entier; varCurrency :result:=reel; varBoolean :result:=booleen; varString :result:=chaine; varvariant :result:=objet; end end; |
Les
propriétés du parent restent visibles lors de la conception.
Une propriété supplémentaire par rapport au TListBox est affichée : TypeObjet. Le programmeur peut sélectionner dans la partie droite de la propriété une des cinq valeurs possibles que nous avons prévues pour cette classe. Ici il a sélectionné la valeur chaine. L’objet est donc une liste de chaînes pendant toute l’exécution du futur programme. |
Dans la partie implementation
nous trouverons :
implementation
procedure register;
le code déjà
indiqué de function GetTypeObjet constructor TlisteVisuGen.create(Aowner:Tcomponent);
...... procedure TlisteVisuGen.supprimer
(k: integer); procedure TlisteVisuGen.inserer
(k: integer; x: T0); function TlisteVisuGen.kieme
(n: integer): T0; .... |
Nous avons réussi notre
contrat qui consistait à mettre à la disposition du programmeur
une classe polymorphe de listes se rapprochant du TAD tel qu’il a été
défini. Il ne nous a pas fallu ajouter beaucoup de code à
la liste visuelle non générique. Tous les opérateurs
sont les mêmes, seule la partie transtypage et nouvelle propriété
sont des nouveautés.
Remarque :
Ce composant n’est qu’une ébauche de liste générique. Il est sécurisé pour les opérateurs que nous avons définis. Il ne faut donc pas utiliser des méthodes internes aux champs que le RAD Delphi ne sait pas masquer directement. Par exemple, le champ items de type Tstrings n’est pas sécurisé vis à vis du type de donnée présent dans la liste. Si l’utilisateur connaît la syntaxe d’une méthode comme LoadFromFile, il peut détourner la protection car cette méthode reste accessible à l’utilisateur. Il lui est donc possible de stocker des données non cohérentes avec le type actuel dans la liste. |
Ce qui veut dire que notre liste générique n’est utilisable d’une manière sécurisée qu’à partir des opérateurs que nous avons définis.
Le lecteur enrichira l’exemple avec ses propres opérateurs.