Developpez.com

Club des développeurs et IT pro
Plus de 4 millions de visiteurs uniques par mois

Developpez.com - Delphi
X

Choisissez d'abord la catégorieensuite la rubrique :


Des Records aux Classes

Date de publication : 18/09/2007 , Date de mise à jour : 18/09/2007

Par Eric Leconte (Clorish) (ericleconte.developpez.com)
 

Cet articles a pour but d'aborder en douceur la création et la manipulation de Classes. Au travers d'un exemple simple : La gestion d'un agenda, nous verrons comment convertir un programme a base de Record en application orientée objet, manipulant des Classes.



I. Introduction

Les Objets (ou Classes) sont souvent considérés par les debutants comme hors de porté de leurs conaissances techniques, preferant à ces dernieres une solution d'aparence plus simple : les Enregistrement (ou Records). Cela est généralement basé sur une méconnaissance des classes.
Le but de ce tutoriel est donc de montrer qu'une classe peut etre tres simple à manipuler.
Afin d'etudier les differences entre Classes et Records, nous allons nous appuyer sur un contexte applicatif simple : Un agenda electronique.
Apres une rapide presentation de l'application basé sur un systeme de donnés géré par des Records, on glissera progressivement vers une structure fondé sur les Classes.

L'application ne sera pas etudié en integralitée afin de nous concentrer sur la gestion des données en mémoire. Simplifié au maximum, notre agenda sera composé d'un ensemble de fiches (Nom, Prenom, Adresse, Numero(s) de telephone, ...) et de quelques fonctions de traitement sur ces fiches. Certaines libertés seront parfois necessaires afin d'illustrer au mieux certains point techniques.

Remarque : Tout au long de cet article, nous allons utiliser le type Record des versions antérieures Delphi 7 et inferieur. Pour des raisons pedagogiques, nous ne tiendrons pas compte des evolution du langages apparues dans les versions Borland Studio 2005 et plus.


II. Conception de l'application de base


II-A. Les déclarations

Notre agenda se repose sur 2 type de variables :
- Les Fiches.
- L'agenda.

Les fiches contiennent les informations relative a une persone : Nom, Prénom, Adresse et Numero(s) de téléphone.
L'agenda lui est constitué d'un ensemble de fiches.

Type
  TFiche = Record
    Nom : String;
    Prenom : String;
    Adresse : String;
    CodePostal : String;
    Ville : String;
    Telephone : String;
  End;
	
  TAgenda = Array of TFiche;
  

II-B. Traitements sur les fiches

Notre application doit effectuer certains traitement de formatage des données contenue dans les fiches :
- Le nom doit etre en majuscule : DUPOND.
- Le prénom doit etre en minuscule, avec la première lettre en majuscule : Alain.
- Le nom complet est composé du nom, suivi du prénom : DUPOND Alain.
- La ville doit etre en majuscule : MARSEILLE.
- L'adresse complete est formatée de la manière suivante : NOM Prenom Adresse CodePostal VILLE

Procedure FormatNom(VAR fiche : TFiche);
Begin
Fiche.Nom := UpperCase(Fiche.Nom);               // Nom en majuscule
End;

Procedure FormatPrenom(Var Fiche : TFiche);
Begin
Fiche.Prenom := LowerCase(Fiche.Prenom);         // Prenom en minuscule 
Fiche.Prenom[1] := UpperCase(Fiche.Prenom[1];    // 1e lettre du prenom en majuscule
End;

Procedure FormatVille(Var Fiche : TFiche);
Fiche.Ville := UpperCase(Ville);                 // Ville en majuscule
End;

Function NomComplet(Fiche : TFiche) : String;
Begin
Result := Fiche.Nom + ' ' + Fiche.Prenom;
End;

Function AdresseComplete(Fiche : TFiche) : String;
Begin
Result := NomComplet + #13#10; 
Result := Result + Fiche.Adresse + #13#10;
Result := Result + Fiche.CodePostal + ' ' + Fiche.Ville;
End;
 

II-C. Traitements sur l'agenda

Notre application effectue egalement destraitements sur l'agenda :
- Ajout d'une fiche.
- Suppression d'une fiche.
- Creation d'une fiche.

Procedure AjouterFiche(Fiche : TFiche);
Var Size : Integer;
Begin
Size := Length(Agenda);
SetLength(Agenda, Size+1);
Agenda[Size] := Fiche;
End;

Procedure SupprimerFiche(Index : Integer);
Var Size : Integer;
Begin
Size := Length(Agenda);
Agenda[Index] := Agenda[Size-1];  // On remplace la fiche a supprimier par la derniere et on reduit le tableau de 1 case
SetLength(Agenda, Size-1];
end;

Function CreerFiche(Nom, PRenom, Adresse, CodePostal, Ville, Telephone) : TFiche;
Begin
Result.nom := Nom;
Result.Prenom := Prenom;
Result.Adresse := Adresse;
Result.CodePostal := CodePostal;
Result.Ville := Ville;
Result.Telephone := Telephone;
FormatNom(Result);
FormatPrenom(Result);
FormatVille(Result);
End;
 

II-D. Optimisation du code

Dans le code presenté au dessus, on peut remarquer plusieurs choses qui peuvent etre optimisé :
- Le passage du parametre "fiche" par adresse (VAR).
- Le besoin recurent d'un parametre "fiche".

Il est necessaire de transmettre la fiche par adresse dans les fonctions de modification sous peine de perdre les modification effectuer. Ce que l'on peut faire c'est manipuler les fiches via des Pointeurs.

Quand au parametre recurent, on peut se servir d'une variable globale que l'on affecterais avant les differents appels aux methodes de traitement.


II-D-1. Gestion des fiches par Pointeurs


Type
  PFiche = ^Fiche;
  TFiche = Record
    {...}
  End;
  
  TAgenda = Array of PFiche;
   
Procedure FormatNom(fiche : PFiche);
Begin
Fiche^.Nom := UpperCase(Fiche^.Nom);               // Nom en majuscule
End;

{...}
 
Procedure SupprimerFiche(Index : Integer);
Var Size : Integer;
Begin
Size := Length(Agenda);
Dispose(Agenda[Index]);           // Liberation de la memoire de la fiche a supprimer
Agenda[Index] := Agenda[Size-1];  // On remplace la fiche a supprimer par la derniere et on reduit le tableau de 1 case
SetLength(Agenda, Size-1];
end;

Function CreerFiche(Nom, PRenom, Adresse, CodePostal, Ville, Telephone) : PFiche;
Begin
New(Result);                  // Allocation de la memoire
Result^.nom := Nom;
Result^.Prenom := Prenom;
{...}
FormatNom(Result);
FormatPrenom(Result);
FormatVille(Result);
End;
 
Remarque : On notera l'usage de 2 fonctions supplementaire : New et Dispose responsable de l'allocation memoire d'une fiche et de sa liberation. On en considère plus les fiches comme des variables complexes, mais comme des poiteurs vers une zone memoire qui contient les données.


II-D-2. Simplification du parametre recurent "fiche"


Var Selected : PFiche;   // Variable globale designant la fiche selectionnée

Procedure FormatNom;
Begin
Selected^.Nom := UpperCase(Selected^.Nom);               // Nom en majuscule
End;

{...}

Function CreerFiche(Nom, PRenom, Adresse, CodePostal, Ville, Telephone) : PFiche;
Begin
New(Result);                  // Allocation de la memoire
Result^.nom := Nom;
Result^.Prenom := Prenom;
{...}
Selected := Result;
FormatNom;
FormatPrenom;
FormatVille;
End;

III. Migration de l'application vers une structure "Objet"


III-A. Les déclarations

Convertir une application basé sur un type Record vers un type Class est tres simple :

Type
  PFiche = ^TFiche;
  TFiche = Record
    Nom : String;
    Prenom : String;
    Adresse : String;
    CodePostal : String;
    Ville : String;
    Telephone : String;
  End;
  
  TAgenda = Array of PFiche;

Var Selected : PFiche;
 

Type
  TFiche = Class
    Nom : String;
    Prenom : String;
    Adresse : String;
    CodePostal : String;
    Ville : String;
    Telephone : String;
  End;
  
  TAgenda = Array of TFiche;

Var Selected : TFiche;
 
En fait le code diffère juste par l'emploi du mot clef Class en lieu et place du mot clef Record.
On notera la simplification du code qui ne necessite plus de type "Pointeur". En effet, les objets (variables de type Class) sont deja des pointeur vers une structure mémoire contenant les donnée de l'objet.


III-B. Manipulation des Objets

Si on considère un objet comme une variable de type Pointeur sur un Record, le code précédent reste quasiement inchangé. Par contre le côté Pointeur implicite des objets permet encore une fois d'alleger le code.

Procedure FormatNom;
Begin
Selected.Nom := UpperCase(Selected.Nom);               // Nom en majuscule
End;

{...}
 
Procedure SupprimerFiche(Index : Integer);
Var Size : Integer;
Begin
Size := Length(Agenda);
Agenda[Index].Destroy;           // Liberation de la memoire de la fiche a supprimer
Agenda[Index] := Agenda[Size-1];  // On remplace la fiche a supprimer par la derniere et on reduit le tableau de 1 case
SetLength(Agenda, Size-1];
end;

Function CreerFiche(Nom, PRenom, Adresse, CodePostal, Ville, Telephone) : PFiche;
Begin
Result := TFiche.Create;                 // Allocation de la memoire
Result.nom := Nom;
Result.Prenom := Prenom;
{...}
Selected := Result;
FormatNom;
FormatPrenom;
FormatVille;
End;
On notera l'absence du caractere "^" devenu implicite, et un systeme de creation/Liberation de la mémoire légèrement different :
New(Variable) deviens Variable := Type.Create;
Dispose(Variable) deviens Variable.Destroy;

Les methodes Create sont dite Constructeur car elles construisent les objets.
Les methodes Destroy sont dites Destructeur car elles detruisent les objets.


III-C. Optimisation des procedures et fonctions

Maintenant que notre application manipule des objets à la place d'enregistrements, il nous reste plus qu'a voir comment optimiser les appels aux procédures et fonctions.
On s'est rendu compte dans les paragraphes precedents que ces fonctions sont toutes liés a des variables ou plutot des types :
- FormatNom : TFiche
- FormatPrenom : TFiche
- FormatVille : TFiche
- NomComplet : TFiche
- AdresseComplete : TFiche
- AjouterFiche : TAgenda
- SupprimerFiche : TAgenda
- CreerFiche : TAgenda

En effet, chacune de ces fonctions necessitent : Soit un parametre récurent de meme type, Soit s'appuyent sur une meme variable globale.
Dans ce cas, il semblerais logique de regroupper et d'associer ces fonction a nos données. Ces fonctions vont donc devenir des methodes :

Type
  TFiche = Class
    Nom : String;
    Prenom : String;
    Adresse : String;
    CodePostal : String;
    Ville : String;
    Telephone : String;
    Procedure FormatNom;
    Procedure FormatPrenom;
    Procedure FormatVille;
    Function NomComplet : String;
    Function AdresseComplete : String;
  End;
  
Procedure TFiche.FormatNom;
Begin
{...}
End;

{...}
  
Pour devenir une méthode, une fonction a tout juste besoin d'être declarée dans notre classe (anciennement Record) et d'etre précédé du nom de la classe en question lors de son implementation. De cette manière, on peux declarer plusieurs fonctions de même non, dans des classes differentes sans soucis de collision.

L'appel à ces methodes se fera donc au travers d'une instance :

Var Fiche : TFiche
Fiche.FormatNom;

III-D. Référence à l'instance d'une classe

Le fait que ces méthodes nécéssite une instance pour etre appelée,



Valid XHTML 1.1!Valid CSS!

Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.
Responsables bénévoles de la rubrique Delphi : Gilles Vasseur - Alcatîz -