|
u_c_tlist - John COLIBRI.
|
- mots clé:template - modèle - encapsulation de tList
- logiciel utilisé: Windows 98, Delphi 5.0
- matériel utilisé: Pentium 500Mhz, 128 M de mémoire
- champ d'application: Delphi 1 à 6 sur Windows, Kylix
- niveau: débutant en Pascal et Delphi
- uses: u_c_basic_object, u_c_display, (u_c_log,
u_loaded, u_strings, u_types_constants, u_c_basic_file,
u_c_file_name, u_dir)
- plan:
1 - Introduction
Le Pascal de WIRTH offre comme structures mémoire de base le tableau et
l'enregistrement. Point de liste chaînée, doublement chaînée, triée, de pile.
La plupart des programmeurs utilisaient donc des listes pointées. Les
techniques objet ont permis de généraliser ces listes avec les "conteneurs" qui
isolaient dans une unité l'architecture de chaînage, et dans une autre unité la
"charge utile" de chaque cellule. Pour employer une nouvelle liste, il
suffisait de redéfinir un nouveau descendant de la cellule de base, et toutes
les opérations d'ajout, balayage etc étaient immédiatement disponibles (pour
une syntaxe simple, voir les transparent
perfectionnement pascal. Pascalissime avaient abondamment utilisé ce type
de techniques.
Avec l'arrivée de Delphi, les nouvelles structures tList et tStringList on
rendu les structures chaînées moins cruciales. L'avantage des tList est la
facilité d'emploi: elles se manipulent comme un tableau, sans avoir à se
préoccuper par la gestion de la taille.
Le seul inconvénient est que le type des cellules est Pointer, et que lorsque
nous plaçons des informations dans une structure pointée par ce Pointer, il
faut surtyper.
La solution simple consiste à encapsuler la tList dans une classe, ce qui a
l'avantage de localiser ce surtypage, les programmes utilisateurs étant vierge
de tout surtypage.
Ce type d'encapsulation étant fréquent, nous avons placé la mécanique
d'encapsulation dans une unité "squelette" qui peut être réutilisée et enrichie
sans avoir à retaper les quatre ou cinq procédures de base.
2 - Utilisation
2.1 - Un exemple simple
Supposons que nous souhaitions manipuler une liste de valeurs numériques
entières.
Pour utiliser le squelette u_c_xxx_list:
- chargez l'unité u_c_xxx_list dans Delphi
- sauvegardez dans votre répertoire sous u_c_value_list
- renommez partout "xxx" en "value"
- ajoutez u_c_value_list à votre projet
- compilez
Il faut à présent personnaliser les éléments de la liste, en ajoutant à chaque
cellule c_value un attribut entier:
- ajoutez le champ à m_value à c_value
- modifiez le CONSTRUCTOR et la fonction d'affichage pour pouvoir manipuler
des entiers. La classe est à présent la suivante:
type c_value= // one "value"
Class(c_basic_object)
public
m_value: Integer;
Constructor create_value(p_name: String; p_value: Integer); Virtual;
function f_display_value: String;
Destructor Destroy; Override;
end;
Constructor c_value.create_value(p_name: String; p_value: Integer);
begin
Inherited create_basic_object(p_name);
m_value:= p_value;
end; // create_value
function c_value.f_display_value: String;
begin
Result:= Format('%-10s %5d', [m_name, m_value]);
end; // f_display_value
Destructor c_value.Destroy;
begin
InHerited;
end; // Destroy
|
La classe c_value_list n'a pas besoin d'être modifiée.
Pour utiliser la nouvelle liste:
- importez u_c_value_list dans votre unité
- créez la classe c_value_list, ajoutez quelques valeurs et affichez le tout:
uses u_c_value_list;
...
procedure TForm1.integer_list_Click(Sender: TObject);
begin
with c_value_list.create_value_list('test_u_c_value_list') do
begin
add_value(c_value.create_value('element_a', 11));
add_value(c_value.create_value('element_b', 12));
display_value_list;
Free
end; // with c_value_list
end;
|
Connaissant le type de chaque cellule, il peut être souhaitable de faciliter la
création d'une cellule, en fournissant à c_value_list les éléments de la
nouvelle cellule:
- ajoutez à c_value_list une méthode ajoutant une cellule avec une String et
un Integer. L'unité devient:
type c_value_list= // "value" list
Class(c_basic_object)
public
...
procedure add_the_value(p_string: String; p_value: Integer);
end;
...
procedure c_value_list.add_the_value(p_string: String; p_value: Integer);
begin
m_c_value_list.add(c_value.create_value(p_string, p_value));
end; // add_the_value
|
Et le programme principal peut à présent ajouter une nouvelle valeur par:
procedure TForm1.integer_list_Click(Sender: TObject);
begin
with c_value_list.create_value_list('test_u_c_value_list') do
begin
add_the_value('element_c', 13);
...
Free;
end; // with c_value_list
end;
|
2.2 - La classe c_xxx
La définition de la cellule de base c_xxx est:
type c_xxx= // one "xxx"
Class(c_basic_object)
public
Constructor create_xxx(p_name: String); Virtual;
function f_display_xxx: String;
Destructor Destroy; Override;
end;
|
Pour les données:
- aucune donnée n'a été introduite (en dehors de m_name qui fait partie de
tous nos objets). Ce sont les cellules renommées qui contiendront des
attributs
Et pour les méthodes:
- create_xxx assure la création (le CONSTRUCTOR sera en général modifié
pour ajouter les attributs propres à la cellule utilisée, comme vu plus
haut)
- f_display_xxx retourne simplement une String pour permettre les affichages
de mise au point
- le DESTRUCTOR n'a rien à faire puisqu'aucune données n'est allouée dans
cette cellule élémentaire
2.3 - La classe c_xxx_list
L'interface est la suivante:
c_xxx_list= // "xxx" list
Class(c_basic_object)
public
m_c_xxx_list: tList;
Constructor create_xxx_list(p_name: String); Virtual;
function f_xxx_count: Integer;
function f_c_xxx(p_xxx: Integer): c_xxx;
procedure add_xxx(p_c_xxx: c_xxx);
procedure display_xxx_list;
Destructor Destroy; Override;
end;
|
Pour les données:
- m_c_xxx_list est la tList. Cet attribut pourrait d'ailleurs être en
PRIVATE.
Et pour les méthodes:
- create_xxx_list alloue la tList
- f_xxx_count: retourne le nombre d'éléments. Il serait possible d'utiliser
m_c_xxx_list.Count directement, mais la création d'une fonction encapsule
mieux notre tList: le programme utilisateur devient indépendant de notre
choix de mise en oeuvre de liste
- f_c_xxx: retourne une cellule élémentaire lorsque l'utilisateur fournit
l'indice
- add_xxx: ajoute une cellule créée par l'utilisateur
- display_xxx_list: affiche tous les éléments
2.4 - Répertoires et Directives de Compilation
L'unité est prévue pour être placée dans:
C:
programs
colibri_skelettons
colibri_helper_skelettons
classes
Vous pouvez naturellement changer cette organisation par Projet | Options |
Directories
Les directives de compilation sont:
- R+ (vérification des intervalles)
- S+ (vérification de la pile)
- pas d'optimisation
3 - Programmation
La classe c_xxx_list applique simplement le principe d'encapsulation:
- f_xxx_count renvoie tList.Count:
function c_xxx_list.f_xxx_count: Integer;
begin
Result:= m_c_xxx_list.Count;
end; // f_xxx_count
|
- et f_c_xxx renvoie le pointeur surtypé (c'est LA le réel bénéfice de toute
cette unité):
function c_xxx_list.f_c_xxx(p_xxx: Integer): c_xxx;
begin
Result:= c_xxx(m_c_xxx_list[p_xxx]);
end; // f_c_xxx
|
Notons que nous pourrions effectuer des vérifications d'intervalle dans
f_c_xxx, en retournant NIL en cas d'erreur.
Le programme utilisant cette unité est totalement indépendant du fait que c'est
une tList qui a été utilisée. Il pourrait être réécrit avec une autre unité
employant des listes chaînées (à condition d'ajouter à cette classe les mêmes
procédures, et entre autre celle qui compte les cellules ou qui accède à une
cellule par son indice).
Allez, je n'ai pas pu résister à la tentation d'ajouter une liste pointée.
L'objectif est que le programme principal ne soit pas modifié. Cette liste est
dans u_cell_list_with_pointers. Sa programmation est celle d'une liste chaînée
classique avec ajout en fin (seules les modifications sont présentées ici):
type c_cell= // one "cell"
Class(c_basic_object)
public
m_c_next_cell: c_cell;
...
end;
c_cell_list= // "cell" list
Class(c_basic_object)
public
m_c_root_cell, m_c_end_cell: c_cell;
m_cell_count: Integer;
...
end;
function c_cell_list.f_cell_count: Integer;
begin
Result:= m_cell_count;
end; // f_cell_count
function c_cell_list.f_c_cell(p_cell: Integer): c_cell;
var l_index: Integer;
begin
l_index:= 0;
Result:= m_c_root_cell;
while (Result<> nil) and (l_index< p_cell) do
begin
Inc(l_index);
Result:= Result.m_c_next_cell;
end; // while
end; // f_c_cell
procedure c_cell_list.add_cell(p_c_cell: c_cell);
// -- add at the END of the list
begin
// -- handle first addition differently
if m_c_root_cell= nil
then m_c_root_cell:= p_c_cell
else m_c_end_cell.m_c_next_cell:= p_c_cell;
// -- update m_c_end_cell
m_c_end_cell:= p_c_cell;
Inc(m_cell_count);
end; // add_cell
procedure c_cell_list.display_cell_list;
var l_c_cell: c_cell;
begin
display(m_name+ ' '+ IntToStr(f_cell_count));
l_c_cell:= m_c_root_cell;
while l_c_cell<> nil do
begin
display(l_c_cell.f_display_cell);
l_c_cell:= l_c_cell.m_c_next_cell;
end; // while
end; // display_cell_list
Destructor c_cell_list.Destroy;
var l_c_kill_cell: c_cell;
begin
while m_c_root_cell<> nil do
begin
l_c_kill_cell:= m_c_root_cell;
m_c_root_cell:= m_c_root_cell.m_c_next_cell;
l_c_kill_cell.Free;
end; // while
Inherited;
end; // Destroy
|
Et la troisième procédure de notre projet de test est effectivement identique à
celle mettent en oeuvre une tList.
4 - Améliorations
Si les éléments de chaque cellule contiennent des données sans traitement
majeur à effectuer sur ces données, il peut être plus simple d'utiliser un
RECORD pointé pour chaque cellule. Nous faisons l'économie de l'écriture du
CONSTRUCTOR et du DESTRUCTOR. Et bénéficions d'une réduction de
consommation mémoire (les octets des tObject, et en plus le champ m_name que
nous avons ajouté à tous nos objets).
L'idée d'encapsuler une tList n'est pas nouvelle. Il est vraissemblable que
d'autres programmeurs aient proposés des solutions plus sophistiquée que le
copier / coller. Un Expert Delphi vient immédiatement à l'esprit. Mais la
solution utilisant un squelette a l'avantage de la simplicité.
5 - Télécharger
Vous pouvez télécharger:
Comme d'habitude:
- nous vous remercions de nous signaler toute erreur, inexactitude ou
problème de téléchargement en envoyant un e-mail à jcolibri@jcolibri.com. Les corrections
qui en résulteront pourront aider les prochains lecteurs
- tous vos commentaires, remarques, questions, critiques, suggestion
d'article, ou mentions d'autres sources sur le même sujet seront de même
les bienvenus à jcolibri@jcolibri.com.
- plus simplement, vous pouvez taper (anonymement ou en fournissant votre
e-mail pour une réponse) vos commentaires ci-dessus et nous les envoyer en
cliquant "envoyer" :
- et si vous avez apprécié cet article, faites connaître notre site,
ajoutez un lien dans vos listes de liens ou citez-nous dans vos réponses
sur les messageries. C'est très simple: plus nous aurons de visiteurs et de
références Google, plus nous écrirons d'articles.
L'auteur
John COLIBRI est passionné par le développement
Delphi et les applications de Bases de Données. Il a écrit de nombreux livres
et articles, et partage son temps entre la
développement de projets pour
ses clients, le conseil et la formation. Son site contient des articles
avec code source, ainsi que le programme et le calendrier des
stages
de formation Delphi, base de données, Ado.Net, Asp.Net et UML qu'il
anime personellement tous les mois, à Paris, en province ou sur site client.
|