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:
- u_c_tlist.zip: l'unité u_c_tlist seule (4 K)
- test_u_c_tlist.zip: le projet de test (18 K) qui contient:
- l'unité
- toutes les unités qu'elle utilise
- le projet de démonstration
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
blogs ou 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 le développement de projets (nouveaux projets, maintenance, audit, migration BDE, migration Xe_n, refactoring) pour ses clients, le
conseil (composants, architecture, test) 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, programmation objet, Services Web, Tcp/Ip et
UML qu'il anime personellement tous les mois, à Paris, en province ou sur site client. |