PROGRAMMATION ORIENTÉ OBJET
La programmation orienté objet ou plus communément appelé poo est devenu essentiel. Les gains
de temps et d'argent obtenues par une telle approche ont contribué à son ascension. La poo
tente de modéliser des objets de la vie courante en des objets logiciels. C'est une manière
complètement différente de penser. Même si au début, c'est un peu plus complexe que la
programmation procédurale, la maîtriser est à la porter de tous.
But
La poo tente de résoudre quelques lacunes de la programmation procédurale. Différents
facteurs font qu'une solution orienté objet est plus adapté qu'une solution à l'ancienne.
C'est évident que chaque facteur ne peut atteindre un seuil d'utilisation parfait dans une
application. Il faut savoir doser chaque facteur en fonction de nos besoins présents et à venir.
Avantage de la poo
Réutilisabilité
Il arrive souvent en programmation qu'il y ait du code qui revient sans cesse de projet
à projet. Habituellement, on copierait le code nécessaire dans notre projet en cours. Avec la
poo, on ne copie pas le code nécessaire, on crée une classe (un genre de structure avec
des fonctions et des procédures) qu'on instanciera avant de l'utiliser. Par la suite,
on pourra utiliser cette classe dans tous nos autres projets et même y rajouter des
fonctionnalités si on le souhaite. Plus nos classes seront réutilisables moins qu'on
codera dans le futur.
Compatiblité
Il est facile de constater l'incompatibilité qu'il existe dans le domaine de l'informatique.
Des logiciels conçus pour mac sont inutilisables sous PC... Palier à une telle lacune peut
selon un certain seuil d'efficacité être envisageable avec la poo. Ce qu'on crée doit
pouvoir fonctionner avec un maximum de classe.
Facilité d'utilisation
Étant donné que la réutilisabilité est primée en poo, on doit s'attendre que d'autre
programmeur puisse un jour utilisé nos classes. Pour ne pas décourager les autres et rendre
les mises à jour plus aisés, c'est important de garder nos classes simples. Une bonne documentation
est des commentaires ne sont jamais de trop.
Classe
Les classes sont des types de données que le programmeur créera. Ces types de données
contiennent des fonctions et des procédures qu'on désigne par des méthodes. Vous avez
certainement déjà utilisé des classes sans vous en rendre compte. En créant votre interface
graphique sous Delphi, cette dernière utilise de nombreuses autres classes. Plaçons une TListbox sur
un formulaire. Ce code est automatiquement créé.
ListBox1: TListBox;
Dans cet exemple, TListBoxest une classe.
Objet
Un objet est tout simplement une instance, variable d'une classe. Dans l'exemple précédent, l'objet serait ListBox1
Méthode, propriété
Écrivez quelque part dans le code ListBox1 ensuite appuyé sur le . Vous verrez comme
sous les illustrations ci-dessous toutes les méthodes et propriétés de la classe.
Méthodes
Propriété
Les propriétés sont les caractéristiques de la classe. Pour la classe TListBox, on
a la propriété MultiSelect. Cette caractéristique
permet de spécifier si on veut permettre la multi-sélection dans la liste.En assignant true
à cette propriété, la liste permettra de sélection plusieurs éléments. En ajoutant items.add,
on accède à une méthode qui permet d'ajouter un élément de type string dans la liste.
Un autre exemple serait une voiture. Une voiture peut avoir plusieurs caractéristiques
comme : transmission, turbo, vitre électrique,vitesse. Pour les méthodes, on pourrait avoir :
démarrer,rouler,arrêt.
Opération sur les classes
Utilisation
Les objets demandent des opérations additionnelles pour qu'il puisse
fonctionner correctement.
Lorsqu'on utilise des composés comme TListBox, TLabel... et qu'on les
dépose sur notre
fiche, Delphi s'occupe de certaines opérations pour nous. Ces opérations
qui sont la construction
et la destruction d'objet doivent être faites par le programmeur lorsque
nous instancions n'importe quel objet tel que TListBox, TLabel... à
l'exécution.
Construction
Lorsqu'on désire construire un objet, on doit utiliser la méthode create. Il peut arriver
que cette méthode nécessite des paramètres. Cette méthode initialise les données de l'objet
lors de sa création. Il est possible de surcharger un constructeur. Delphi met nos données
automatiquement à zéro, si on veut que nos données soient initialisées à d'autres valeurs, on
crée un constructeur.
Un modèle de constructeur pourrait ressembler à ceci
>objet := classe.constructeur[(paramètres)];
Destruction
Un destructeur sert à détruire un objet. Cet objet ne pourra plus être utilisé par la suite.
Le destructeur a une syntaxe semblable à un destructeur.
objet := objet.destructeur[(parametres)];
Il est rare que l'on doive créer un destructeur car on peut le détruire en appelant la
méthode Free. Avant d'appeler cette méthode, on devrait toujours mettre l'objet à nil puisque
que l'objet ne connaît pas les variables qui peuvent le référencer.
A partir de Delphi 5, on peut faire appel à la méthode FreeAndNil(objet). En mettant l'objet
à nil, il n'y aura pas d'erreur si on tente de détruire l'objet plusieurs fois.
Contrôle d'accès
En programmation procédurale, n'importe qui peut modifier à peu près n'importe quoi.
Il n'est pas rare de chercher pourquoi une variable à une certaine valeur et de se rendre
compte que dans le code que son contenu est modifié. Beaucoup d'erreurs peuvent facilement
intervenir. En poo, on a vu restreindre ce type d'erreur en permettant d'avoir un certain
contrôle sur le contenu d'une classe. Les données ne peuvent être modifiées que par des méthodes.
L'accès aux données est ainsi indirect, leur sécurité est accrue. Delphi permet quatre types
d'accès aux données.
- Private
- Public
- Protected
- Published
Nous discuterons seulement des 3 premiers dans ce chapitre. Le dernier étant réservé
principalement pour les composants. Les données private et protected sont accessible à
n'importe quelles méthodes qui sont dans la même unité. Nous devrions donc mettre une classe
par unité, sinon l'accès aux données n'est pas vraiment sécuritaire.
Ceux qui utiliseront nos classes verront seulement ce que nous voudrons.
Rien ne sert de donner accès à tout ce qui est dans la classe si seulement quelques données
doivent être modifiées. Un exemple très simple pourrait être le cas d'un ordinateur.
On n'a pas besoin de savoir comment il fonctionne ni comment les données s'affichent à l'écran.
On a accès à une souris, clavier et on peut faire tout le travail que l'on désire. Le contrôle
d'accès des données est aussi appelé encapsulation.Grâce à l'encapsulation, on cache le
fonctionnement interne (implantation) de la classe.
private
Les données et les méthodes qui sont privés ne sont pas accessibles à l'extérieur d'une
unité. On n'a pas besoin de savoir comment les données
se transigent par le bus... pour utiliser l'ordinateur. Savoir cette information n'est pas pertinente pour utiliser
l'ordinateur.
public
Le clavier, les haut-parleurs, la souris et l'écran seraient publics. Grâce à eux, on peut
exécuter de nombreuses tâches. N'importe qui peut utiliser les objets ou méthodes déclarés
public. Il faut bien choisir ce qu'on mettra dans cette section.
protected
Lorsqu'on overclock un ordinateur, seul quelques informations techniques sont nécessaires.
Nul besoin de savoir comment l'information circule dans le processeur. Il faut ouvrir le
boîtier et faire quelques modifications. Les données et méthodes protected sont seulement
accessibles à la classe et ses sous-classes.
Voici un exemple pour mieux comprendre le fonctionnement des classes et de l'encapsulation des données.
unit unitAgenda;
interface
uses
SysUtils;
type
TAgenda = class
private
Annee:string;
public
procedure SetAnnee(const a:string);
constructor create;
end;
implementation
constructor TAgenda.create;
begin
Annee:= DateToStr(Date);
end;
procedure TAgenda.SetAnnee(const a:string);
begin
Annee:=a;
end;
end.
Après avoir créé notre classe, on peut l'utiliser dans un programme.
var agenda:TAgenda;
begin
agenda := TAgenda.Create;
agenda.annee := 2005; //[Erreur] Unit1.pas(29): Identificateur non déclaré : 'annee'
FreeAndNil(agenda);
end;
Dans l'exemple ci-dessus, on tente d'assigner une valeur au champ annee. Cette propriété
est privée, on ne peut directement accéder par elle. Delphi nous envoie un message d'erreur.
La bonne façon est d'employer la méthode SetAnnee.
var
agenda:TAgenda;
begin
agenda := TAgenda.Create;
agenda.SetAnnee('2005');
FreeAndNil(agenda);
end;
Héritage
Nous avons vu au début de ce chapitre, que la réutilisation est très importante. Souvent,
on ne modifie que quelques lignes dans une fonction ou procédure pour qu'elle puisse fonctionner
dans un nouveau programme. On doit alors tout copier et faire les modifications
nécessaires. En poo de tel changement s'effectue grâce à l'héritage. Rien ne sert d'inventer
la roue à chaque fois. Lorsqu'on fait du copier-coller, on oublie souvent des morceaux.
L'héritage empêche de faire de telles erreurs. On peut ajouter de nouvelles fonctionnalités,
fonctions, procédures à une classe existante en créant une nouvelle classe qui se basera
sur la première. Je vais utiliser l'exemple classique utilisé pour expliquer l'héritage en poo.
J'ai eu cet exemple en langage C++ et java.
La classe ancêtre, de base sur l'image ci-dessus est Forme. Les classes oval, rectangle
et ligne sont dérivées de forme, on les surnomme classes dérivées ou classes enfants. La classes
oval est l'ancêtre de cercle. Une telle approche permet de regrouper des points en commun
dans une classe. Cette classe servira par la suite en a crér d'autres plus spécialisés. On
peut continuer le schéma en ajoutant une classe carré. Un carré est en fait un rectangle
avec 4 côtés identiques.
En programmation procédurale, on aurait eu probablement beaucoup de structure et elles
auraient de nombreuses variables. Beaucoup de code aurait été redondant. L'héritage permet
d'éviter la redondance et d'utiliser ce qui a déjà été créé. On peut sans cesse rendre les
objets de plus en plus spécialisés.
Nous allons utiliser un exemple plus simple ensuite nous continuerons avec les formes
pour des concepts plus avancés.
Le premier gameboy fonctionnait en noir et blanc. On pouvait l'ouvrir, l'éteindre et il y avait du son.
Lorsque la deuxième version sortit, les couleurs avaient apparu dans les jeux. On pouvait
toujours l'ouvrir, l'éteindre et le son était encore de la partie. Rien ne sert de réécrire
ce qui n'a pas changé. Faut seulement ajouter les nouvelles fonctionnalités.
Il suffit de rajouter le nom de la classe qu'on veut hériter en parenthèse à côté
du mot réservé class.
[nom de class] = class(nom de class ancêtre)
La classe gameboy ressemblerait à
TGameBoy = class
protected
Alumer:boolean;
Son:boolean;
public
procedure Ouvrir;
procedure Fermer;
end;
Celle du gameboy color à
TGameBoyColor = class(TGameBoy)
private
Couleur:Integer;
public
procedure SetCouleur(Coul:Integer);
function GetCouleur:Integer;
end;
Les variables et méthodes de TGameBoy sont accessibles à la classe TGameBoyColor. Il
suffit d'instancier TGameBoyColor ensuite de taper le «.» et on verra les propriétés et
méthodes accessibles.
Si nous avions voulu ajouter, modifier des fonctionnalités aux méthodes existantes de TGameBoy,
il aurait fallu les déclarer virtual.
TGameBoy = class
protected
Alumer:boolean;
Son:boolean;
public
procedure Ouvrir;virual;
procedure Fermer;virtual;
end;
Dans la classe TGameBoyColor, il faut ajouter le mot override; après les méthodes pour pouvoir les surcharger.
TGameBoyColor = class(TGameBoy)
private
Couleur:Integer;
public
procedure Ouvrir;virual;override;
procedure Fermer;virtual;override;
procedure SetCouleur(Coul:Integer);
function GetCouleur:Integer;
end;
Après les avoir surchargés, on a 2 possibilités, ajouter du code à celui existant ou réécrire complètement la
méthode. Si on ajoute du code, il faut mettre le mot inherited. Dans le cas contraire, on inscrit le code qu'on désire.
procedure Open;
begin
inherited;
GetCouleur;
end;
Tout le code de la méthode open de la classe TGameBoy est exécuté plus le nouveau bout
de code. Nul besoin de tout recopier le code, ajouter un seul mot suffit.
Polymorphisme
Le polymorphisme permet d'appeler une méthode commune à un groupe d'objet (héritage).
Nul de besoin de se soucier de la classe à appeler, le programme détermine dynamiquement
quelle méthode appelée selon la méthode.
Méthode abstraite
Les méthodes abstraites sont très importantes pour profiter pleinement du polymorphisme.
Le mot abstract est utilisé en Delphi pour une classe déclarée mais non défini dans une
classe ancêtre. Seulement ses sous-classes la définiront. Dans l'exemple antérieur sur
les formes, TForme pourrait avoir une méthode dessiner mais elle ne serait pas définie.
Rectangle, Oval, ligne... auraient aussi cette méthode pleinement définie. Alors si
nous créons un tableau de TForme et que nous y insérons des rectangles, carrés, cercles...
En appelant la méthode dessine de TForme, on pourrait dessiner toutes les formes
sans connaître d'avance si c'est un rectangle, cercle carré.
Un programme est disponible
ici
Nous pouvons remarquer dans ce programme que la méthode dessine est déclarée abstract.
Comme je l'ai dit, une méthode abstract doit seulement être déclarée. Les autres
héritent de TForme. Elles doivent mettre le mot override à leurs méthodes dessiner, on peut
ensuite les définir. Regardez ce code assez simple (j'ai tout mis dans la même unité) et
essayé de le comprendre.