Agrégation, Composition avec Delphi

Voici une illustration de la belle mécanique offerte par Delphi en ce qui concerne l'application des notions d'UML que sont: l'agrégation et la composition d'objet

Article lu   fois.

L'auteur

Profil ProSite personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

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é avec un magnétoscope (Deux en un)
  • Télé combiné 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
Diagramme UML
Diagramme UML

Pour ne pas surcharger le schéma inutilement, la classe TTeleComboMagnetoscope a été omise volontairement.

II-C. Explications

Un certain nombre de remarques peut être fait au sujet de ce diagrmame lorsqu'on débute.

Les principales remarques sont :
  • Quelle diffrérence y-t-a-il entre le losange rempli et le losange vide ?
  • A 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/Arret, 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 :

 
Sélectionnez
  { 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 :

 
Sélectionnez
  { 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 ensuite notre composition avec un TTeleComboLecteurDVD.

III-B-1. Agrégation :

 
Sélectionnez
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'éventuel utilisation, 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 :

 
Sélectionnez
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  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 délphi.
Même remarque pour le TComponent.SetSubComponent() mais touchons 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 :

 
Sélectionnez
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 :

 
Sélectionnez
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) :

 
Sélectionnez
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;

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

Articles consultés :
Cours UML - Diagramme de classe
Céation de composants
Les Design Patterns avec ModelMaker de Delphi 7

  

Copyright © 2005 Neil Baumberger. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.