I. Rappels en UML▲
Inutile de refaire un travail déjà très bien fait. Donc, si vous avez besoin de revoir (ou d'apprendre) ces notions d'UML, rien ne vaut : Agrégation et Composition en UML.
II. L'exemple en théorie▲
II-A. But à atteindre▲
Modéliser les objets suivants :
- magnétoscope ;
- télé ;
- lecteur DVD ;
- télé combinée avec un magnétoscope (deux en un) ;
- télé combinée avec un lecteur DVD (deux en un).
Contrainte : il faudrait pouvoir brancher aussi bien un magnétoscope qu'un lecteur DVD sur la télé normale.
II-B. Modélisation▲
Plusieurs approches sont, comme toujours, possibles : cela dépend du besoin réel.
Ici nous tentons d'illustrer l'agrégation et la composition, ainsi, nous pouvons commencer par regrouper nos objets et les catégoriser.
Lecteur de média :
- magnétoscope ;
- lecteur DVD.
Type de télévision :
- télé de base ;
- télé combo lecteur DVD ;
- télé combo magnétoscope.
Pour ne pas surcharger le schéma inutilement, la classe TTeleComboMagnetoscope a été omise volontairement.
II-C. Explications▲
Un certain nombre de remarques peuvent être faites au sujet de ce diagramme lorsqu'on débute.
Les principales remarques sont :
- quelle différence y a-t-il entre le losange rempli et le losange vide ?
- à quoi servent les classes TCustomXXX ?
II-C-1. Losange rempli, losange vide ?▲
Voir ou revoir le cours d'UML car ce sont des symboles propres à UML.
II-C-2. Justifications de l'existence de la classe TCustomTele▲
En prévision d'éventuelles propriétés telles que la diagonale de l'écran, le statut Marche/Arrêt, il est préférable de regrouper ces caractéristiques communes en un seul ancêtre.
Ensuite, les descendants n'auront plus qu'à faire leur travail spécifique.
Pour TTele
- Mettre à disposition une agrégation avec un TcustomLecteur.
Pour les TTeleComboXXX
- Étant composés d'un lecteur, ils doivent créer et libérer leur propre lecteur.
II-C-3. Justifications de l'existence de la classe TCustomLecteur▲
Une classe ancêtre est nécessaire afin de centraliser un certain nombre de caractéristiques et aussi dans l'optique d'interchanger de lecteur pour une télé normale.
III. L'exemple en pratique▲
Voyons maintenant la mise en pratique de ce modèle UML.
Nous allons donc voir comment Delphi nous propose des mécanismes prévus exactement pour ces besoins de conception.
Les commentaires dans le code sont importants pour la compréhension.
III-A. Déclaration des classes▲
III-A-1. Les télés ▲
{ Classe de base pour tout téléviseur ------------------------------------- }
TCustomTele = class
(TComponent)
private
FAllumee: Boolean
;
FDiagonaleCM: Integer
;
public
constructor
Create(AOwner: TComponent); override
;
published
property
Allumee: Boolean
read
FAllumee write
FAllumee;
property
DiagonaleCM: Integer
read
FDiagonaleCM write
FDiagonaleCM;
end
;
{ Classe standard représentant une télé courante ----------------------------}
TTele = class
(TCustomTele)
private
FLecteur: TCustomLecteur;
procedure
SetLecteur(Value: TCustomLecteur);
protected
// Important :
procedure
Notification(AComponent: TComponent; Operation: TOperation); override
;
public
function
LecteurPresent: Boolean
;
published
// Agrégation d'un lecteur externe à la télé (ex. : Magnétoscope, LecteurDVD)
property
Lecteur: TCustomLecteur read
FLecteur write
SetLecteur;
end
;
{ Les combinés --------------------------------------------------------------}
TTeleComboDVD = class
(TCustomTele)
private
FLecteurDVD: TLecteurDVD;
procedure
SetLecteurDVD(const
Value: TLecteurDVD);
public
constructor
Create(AOwner: TComponent); override
;
published
// Composition d'un LecteurDVD
property
LecteurDVD: TLecteurDVD read
FLecteurDVD write
SetLecteurDVD;
end
;
TTeleComboMagneto = class
(TComponent)
private
FMagnetoscope: TMagnetoscope;
procedure
SetMagnetoscope(const
Value: TMagnetoscope);
public
constructor
Create(AOwner: TComponent); override
;
published
// Composition d'un Magnétoscope
property
Magnetoscope: TMagnetoscope read
FMagnetoscope write
SetMagnetoscope;
end
;
III-A-2. Les lecteurs de médias ▲
{ Classe de base pour tout lecteur de médias --------------------------------}
TCustomLecteur = class
(TComponent)
public
procedure
Eject; virtual
;
procedure
Play; virtual
;
procedure
Stop; virtual
;
end
;
TLecteurDVD = class
(TCustomLecteur)
private
FCompatibleMP3: Boolean
;
published
// Exemple de particularité propre au lecteur DVD
property
CompatibleMP3: Boolean
read
FCompatibleMP3 write
FCompatibleMP3;
end
;
TMagnetoscope = class
(TCustomLecteur)
private
FNbeTetesLecture: Integer
;
published
// Exemple de particularité propre au magnétoscope
property
NbeTetesLecture: Integer
read
FNbeTetesLecture write
FNbeTetesLecture;
end
;
III-B. Implémentation▲
Nous allons nous arrêter sur ce qui concerne notre sujet en commençant par l'agrégation de notre TTele puis notre composition avec un TTeleComboLecteurDVD.
III-B-1. Agrégation▲
procedure
TTele.Notification(AComponent: TComponent;
Operation: TOperation);
begin
inherited
;
// Si un composant nous notifie qu'il est en cours de destruction...
if
Operation=opRemove then
begin
//... et que c'est notre lecteur actuel alors...
if
AComponent=FLecteur then
FLecteur := nil
; // On ne garde pas une référence qui deviendra fausse.
// Ainsi, pour d'éventuelles utilisations, dans la classe TTele, du Lecteur
// il faut bien faire attention que le Lecteur soit toujours
// assigné ( if Assigned(FLecteur) then .... else ... )
end
;
end
;
procedure
TTele.SetLecteur(Value: TCustomLecteur);
begin
// Si c'est un lecteur autre que l'actuel :
if
FLecteur<>Value then
begin
// Si l'actuel existe encore
if
Assigned(FLecteur) then
FLecteur.RemoveFreeNotification(Self
); // Je ne souhaite plus être notifié par lui de sa destruction
// Le nouveau lecteur devient l'actuel
FLecteur := Value;
// Si le nouveau est bien un lecteur (et pas rien (nil))
if
Assigned(FLecteur) then
FLecteur.FreeNotification(Self
); // Je souhaite qu'il me notifie à temps de sa destruction éventuelle
end
;
end
;
III-B-2. Composition▲
constructor
TTeleComboDVD.Create(AOwner: TComponent);
begin
inherited
;
// Je crée le lecteur qui m'appartient (ainsi il sera libéré en même temps que moi)
FLecteureDVD := TLecteurDVD.Create(Self
);
// Je précise à Delphi qu'il doit faire persister l'état de l'objet FLecteureDVD
FLecteureDVD.SetSubComponent(True
);
// Pour l'esthétique ou bien dans le cas où j'aurais plusieurs LecteurDVD en SubComponent(True)
// je nomme le composant
FLecteureDVD.Name := 'LecteurDVD'
;
end
;
Pour plus de précisions sur la mécanique du TComponent.Create(AOwner: TComponent) se référer à l'aide Delphi.
Même remarque pour le TComponent.SetSubComponent() mais touchons-en malgré tout quelques mots.
Il faut savoir que le code FLecteureDVD.SetSubComponent(True); change quelque peu l'état de l'objet FLecteureDVD.
Ainsi, lorsque Delphi tentera de sauvegarder la form sur laquelle est posée notre TTeleComboDVD, il saura qu'il faut également enregistrer l'état de FLecteureDVD (qui est bien sûr une propriété en published).
Même chose lors du chargement de la form, l'état de FLecteureDVD sera rétabli.
III-C. Empaquetage▲
Vous trouverez la façon dont le tout a été rangé dans le fichier sources.zip
Si ce lien ne fonctionne pas chez vous, utilisez celui-ci.zip.
III-D. Phase de test▲
Le test consiste simplement à déposer une instance de chacun des composants qui apparaissent désormais dans la palette à l'onglet 'TV-Vidéo'.
Télé et lecteurs :
- on peut alors « brancher » un magnétoscope ou bien un lecteur DVD à la télé ;
- si l'on supprime un lecteur qui était branché à la télé, tout se passe bien (pas de violation d'accès).
Télés combo :
- nos combos ont leur propre lecteur bien accessible par l'inspecteur d'objet ;
- mais ni interchangeable ni supprimable.
III-E. Constatations▲
Pour les curieux, vous pouvez regarder le .dfm qui nous éclaire sur la différence entre agrégation et composition :
object
Form1: TForm1
Left = 198
Top = 108
Width = 870
Height = 600
Caption = 'Form1'
object
Tele1: TTele
Allumee = False
DiagonaleCM = 45
Lecteur = Magnetoscope1 // Tele1.Lecteur fait référence à l'instance Magnetoscope1
Left = 96
Top = 72
end
object
TeleComboDVD1: TTeleComboDVD
Allumee = False
DiagonaleCM = 45
LecteureDVD.CompatibleMP3 = False
// LecteureDVD est propre à TeleComboDVD1
Left = 96
Top = 160
end
object
Magnetoscope1: TMagnetoscope
NbeTetesLecture = 0
Left = 192
Top = 72
end
end
Conclusion▲
En guise de conclusion, on ne peut que vous inviter à explorer la VCL qui recèle de nombreux trésors d'utilisation de l'approche objet.
Ainsi vous trouverez l'utilisation de SetSubComponent(True) dans l'unité StdActns :
constructor
TCommonDialogAction.Create(AOwner: TComponent);
var
DialogClass: TCommonDialogClass;
begin
inherited
Create(AOwner);
DialogClass := GetDialogClass;
if
Assigned(DialogClass) then
begin
FDialog := DialogClass.Create(Self
);
FDialog.Name := Copy(DialogClass.ClassName, 2
, Length(DialogClass.ClassName));
FDialog.SetSubComponent(True
);
end
;
DisableIfNoHandler := False
;
Enabled := True
;
end
;
et, beaucoup plus fréquemment, l'implémentation de Notification() (exemple : dans l'unité ActnList) :
procedure
TCustomActionList.Notification(AComponent: TComponent;
Operation: TOperation);
begin
inherited
Notification(AComponent, Operation);
if
Operation = opRemove then
if
AComponent = Images then
Images := nil
else
if
(AComponent is
TContainedAction) then
RemoveAction(TContainedAction(AComponent));
end
;