🔝 Retour au Sommaire
Les interfaces sont un concept avancé de la programmation orientée objet qui permet de définir un "contrat" que les classes doivent respecter. Une interface décrit ce qu'un objet peut faire sans spécifier comment il le fait.
Une interface est une définition abstraite d'un ensemble de méthodes et de propriétés qu'une classe doit implémenter. C'est comme un cahier des charges ou un contrat.
Imaginez une prise électrique :
- L'interface = la forme standardisée de la prise (deux ou trois trous)
- Les classes qui implémentent = les différents appareils (lampe, ordinateur, téléphone)
- Tous ces appareils sont différents mais peuvent se brancher sur la même prise car ils respectent le même "contrat"
| Classe | Interface |
|---|---|
| Contient des données (champs) | Ne contient pas de données |
| Contient l'implémentation des méthodes | Définit uniquement la signature des méthodes |
| Une classe peut hériter d'une seule classe | Une classe peut implémenter plusieurs interfaces |
| Peut être instanciée | Ne peut PAS être instanciée |
Une interface se déclare avec le mot-clé interface dans une définition de type :
type
IDessinable = interface
['{3B8F9C12-5E4D-4A3B-9F2E-1C8D5A7B3F9E}'] // GUID
procedure Dessiner;
function ObtenirCouleur: string;
procedure DefinirCouleur(const ACouleur: string);
end;- Nom : par convention, les interfaces commencent par
I(IDessinable, IComparable, etc.) - GUID : un identifiant unique global entre crochets (optionnel mais recommandé)
- Pas d'implémentation : uniquement les signatures des méthodes
- Pas de visibilité : tout est public par défaut
Dans l'IDE Delphi, utilisez Ctrl+Shift+G pour générer automatiquement un GUID. Vous pouvez aussi utiliser cette fonction :
// Pour générer un GUID
uses System.SysUtils;
var
MonGUID: TGUID;
begin
CreateGUID(MonGUID);
ShowMessage(GUIDToString(MonGUID));
end;Une classe implémente une interface en ajoutant le nom de l'interface après sa déclaration :
type
// Déclaration de l'interface
IDessinable = interface
['{3B8F9C12-5E4D-4A3B-9F2E-1C8D5A7B3F9E}']
procedure Dessiner;
function ObtenirCouleur: string;
procedure DefinirCouleur(const ACouleur: string);
end;
// Classe qui implémente l'interface
TCercle = class(TInterfacedObject, IDessinable)
private
FRayon: Double;
FCouleur: string;
public
constructor Create(ARayon: Double);
// Implémentation des méthodes de l'interface
procedure Dessiner;
function ObtenirCouleur: string;
procedure DefinirCouleur(const ACouleur: string);
property Rayon: Double read FRayon write FRayon;
end;
constructor TCercle.Create(ARayon: Double);
begin
inherited Create;
FRayon := ARayon;
FCouleur := 'noir';
end;
procedure TCercle.Dessiner;
begin
ShowMessage(Format('Dessin d''un cercle %s de rayon %.2f',
[FCouleur, FRayon]));
end;
function TCercle.ObtenirCouleur: string;
begin
Result := FCouleur;
end;
procedure TCercle.DefinirCouleur(const ACouleur: string);
begin
FCouleur := ACouleur;
end;TInterfacedObject est une classe de base qui gère automatiquement le comptage de références des interfaces. C'est important pour la gestion mémoire automatique des interfaces.
var
MonCercle: IDessinable; // Variable de type interface
begin
MonCercle := TCercle.Create(5);
// Pas besoin de try...finally avec les interfaces !
// La mémoire est libérée automatiquement
MonCercle.DefinirCouleur('rouge');
MonCercle.Dessiner;
ShowMessage('Couleur : ' + MonCercle.ObtenirCouleur);
// MonCercle sera automatiquement libéré à la fin du bloc
end;Avec les interfaces, vous n'avez pas besoin d'appeler Free ! La mémoire est libérée automatiquement grâce au comptage de références.
Une classe peut implémenter plusieurs interfaces :
type
IDessinable = interface
['{3B8F9C12-5E4D-4A3B-9F2E-1C8D5A7B3F9E}']
procedure Dessiner;
end;
IRedimensionnable = interface
['{7F2A4B8C-1D3E-4F5A-8B9C-2D4E6F8A1B3C}']
procedure Agrandir(Facteur: Double);
procedure Retrecir(Facteur: Double);
end;
IColorable = interface
['{9E4D7C2B-3A5F-4E8D-9C2A-5F7E9D3B8C4A}']
function ObtenirCouleur: string;
procedure DefinirCouleur(const ACouleur: string);
end;
// Classe implémentant trois interfaces
TRectangle = class(TInterfacedObject, IDessinable, IRedimensionnable, IColorable)
private
FLargeur: Double;
FHauteur: Double;
FCouleur: string;
public
constructor Create(ALargeur, AHauteur: Double);
// Méthodes de IDessinable
procedure Dessiner;
// Méthodes de IRedimensionnable
procedure Agrandir(Facteur: Double);
procedure Retrecir(Facteur: Double);
// Méthodes de IColorable
function ObtenirCouleur: string;
procedure DefinirCouleur(const ACouleur: string);
end;
constructor TRectangle.Create(ALargeur, AHauteur: Double);
begin
inherited Create;
FLargeur := ALargeur;
FHauteur := AHauteur;
FCouleur := 'bleu';
end;
procedure TRectangle.Dessiner;
begin
ShowMessage(Format('Rectangle %s : %.2f x %.2f',
[FCouleur, FLargeur, FHauteur]));
end;
procedure TRectangle.Agrandir(Facteur: Double);
begin
FLargeur := FLargeur * Facteur;
FHauteur := FHauteur * Facteur;
end;
procedure TRectangle.Retrecir(Facteur: Double);
begin
Agrandir(1 / Facteur);
end;
function TRectangle.ObtenirCouleur: string;
begin
Result := FCouleur;
end;
procedure TRectangle.DefinirCouleur(const ACouleur: string);
begin
FCouleur := ACouleur;
end;var
MonRectangle: TRectangle;
Dessinable: IDessinable;
Redimensionnable: IRedimensionnable;
Colorable: IColorable;
begin
MonRectangle := TRectangle.Create(10, 5);
try
// Utilisation directe
MonRectangle.Dessiner;
// Affectation à une variable d'interface
Dessinable := MonRectangle;
Dessinable.Dessiner;
Redimensionnable := MonRectangle;
Redimensionnable.Agrandir(2);
Colorable := MonRectangle;
Colorable.DefinirCouleur('vert');
MonRectangle.Dessiner;
finally
MonRectangle.Free;
end;
end;L'opérateur Supports vérifie si un objet implémente une interface donnée :
var
MonObjet: TObject;
Dessinable: IDessinable;
begin
MonObjet := TRectangle.Create(10, 5);
try
// Vérifier si l'objet supporte l'interface
if Supports(MonObjet, IDessinable, Dessinable) then
begin
ShowMessage('L''objet peut être dessiné');
Dessinable.Dessiner;
end
else
ShowMessage('L''objet ne peut pas être dessiné');
finally
MonObjet.Free;
end;
end;On peut aussi utiliser as pour convertir vers une interface :
var
MonRectangle: TRectangle;
Dessinable: IDessinable;
begin
MonRectangle := TRectangle.Create(10, 5);
try
// Conversion vers l'interface
Dessinable := MonRectangle as IDessinable;
Dessinable.Dessiner;
finally
MonRectangle.Free;
end;
end;Les interfaces peuvent hériter d'autres interfaces :
type
IAnimal = interface
['{1A2B3C4D-5E6F-7A8B-9C0D-1E2F3A4B5C6D}']
procedure SeNourrir;
procedure Dormir;
end;
IAnimalDomestique = interface(IAnimal) // Hérite de IAnimal
['{7F8E9D0C-1B2A-3D4E-5F6A-7B8C9D0E1F2A}']
procedure Caresser;
procedure Nommer(const ANom: string);
end;
TChien = class(TInterfacedObject, IAnimalDomestique)
private
FNom: string;
public
// Méthodes de IAnimal
procedure SeNourrir;
procedure Dormir;
// Méthodes de IAnimalDomestique
procedure Caresser;
procedure Nommer(const ANom: string);
end;
procedure TChien.SeNourrir;
begin
ShowMessage(FNom + ' mange des croquettes');
end;
procedure TChien.Dormir;
begin
ShowMessage(FNom + ' dort dans son panier');
end;
procedure TChien.Caresser;
begin
ShowMessage(FNom + ' remue la queue de joie !');
end;
procedure TChien.Nommer(const ANom: string);
begin
FNom := ANom;
end;Les interfaces sont parfaites pour créer des systèmes de plugins :
type
// Interface commune à tous les plugins
IPlugin = interface
['{8D3E4F5A-6B7C-8D9E-0F1A-2B3C4D5E6F7A}']
function ObtenirNom: string;
function ObtenirVersion: string;
procedure Initialiser;
procedure Executer;
procedure Terminer;
end;
// Plugin de sauvegarde
TPluginSauvegarde = class(TInterfacedObject, IPlugin)
public
function ObtenirNom: string;
function ObtenirVersion: string;
procedure Initialiser;
procedure Executer;
procedure Terminer;
end;
// Plugin d'export
TPluginExport = class(TInterfacedObject, IPlugin)
public
function ObtenirNom: string;
function ObtenirVersion: string;
procedure Initialiser;
procedure Executer;
procedure Terminer;
end;
// Implémentation TPluginSauvegarde
function TPluginSauvegarde.ObtenirNom: string;
begin
Result := 'Plugin de sauvegarde';
end;
function TPluginSauvegarde.ObtenirVersion: string;
begin
Result := '1.0';
end;
procedure TPluginSauvegarde.Initialiser;
begin
ShowMessage('Initialisation du plugin de sauvegarde');
end;
procedure TPluginSauvegarde.Executer;
begin
ShowMessage('Sauvegarde des données...');
end;
procedure TPluginSauvegarde.Terminer;
begin
ShowMessage('Plugin de sauvegarde terminé');
end;
// Implémentation TPluginExport
function TPluginExport.ObtenirNom: string;
begin
Result := 'Plugin d''export';
end;
function TPluginExport.ObtenirVersion: string;
begin
Result := '2.0';
end;
procedure TPluginExport.Initialiser;
begin
ShowMessage('Initialisation du plugin d''export');
end;
procedure TPluginExport.Executer;
begin
ShowMessage('Export des données vers Excel...');
end;
procedure TPluginExport.Terminer;
begin
ShowMessage('Plugin d''export terminé');
end;type
TGestionnairePlugins = class
private
FPlugins: TList<IPlugin>;
public
constructor Create;
destructor Destroy; override;
procedure AjouterPlugin(Plugin: IPlugin);
procedure ExecuterTousLesPlugins;
end;
constructor TGestionnairePlugins.Create;
begin
inherited Create;
FPlugins := TList<IPlugin>.Create;
end;
destructor TGestionnairePlugins.Destroy;
begin
FPlugins.Free;
inherited Destroy;
end;
procedure TGestionnairePlugins.AjouterPlugin(Plugin: IPlugin);
begin
FPlugins.Add(Plugin);
end;
procedure TGestionnairePlugins.ExecuterTousLesPlugins;
var
Plugin: IPlugin;
begin
for Plugin in FPlugins do
begin
ShowMessage(Format('Plugin : %s (v%s)',
[Plugin.ObtenirNom, Plugin.ObtenirVersion]));
Plugin.Initialiser;
Plugin.Executer;
Plugin.Terminer;
end;
end;var
Gestionnaire: TGestionnairePlugins;
begin
Gestionnaire := TGestionnairePlugins.Create;
try
// Ajouter des plugins
Gestionnaire.AjouterPlugin(TPluginSauvegarde.Create);
Gestionnaire.AjouterPlugin(TPluginExport.Create);
// Exécuter tous les plugins
Gestionnaire.ExecuterTousLesPlugins;
finally
Gestionnaire.Free;
end;
end;Les interfaces peuvent déclarer des propriétés avec des getters et setters :
type
IPersonne = interface
['{2C4D6E8F-1A3B-5C7D-9E0F-1A2B3C4D5E6F}']
function GetNom: string;
procedure SetNom(const Value: string);
function GetAge: Integer;
procedure SetAge(const Value: Integer);
property Nom: string read GetNom write SetNom;
property Age: Integer read GetAge write SetAge;
end;
TPersonne = class(TInterfacedObject, IPersonne)
private
FNom: string;
FAge: Integer;
function GetNom: string;
procedure SetNom(const Value: string);
function GetAge: Integer;
procedure SetAge(const Value: Integer);
public
property Nom: string read GetNom write SetNom;
property Age: Integer read GetAge write SetAge;
end;
function TPersonne.GetNom: string;
begin
Result := FNom;
end;
procedure TPersonne.SetNom(const Value: string);
begin
FNom := Value;
end;
function TPersonne.GetAge: Integer;
begin
Result := FAge;
end;
procedure TPersonne.SetAge(const Value: Integer);
begin
if (Value >= 0) and (Value <= 150) then
FAge := Value;
end;var
Personne: IPersonne;
begin
Personne := TPersonne.Create;
Personne.Nom := 'Marie Dupont';
Personne.Age := 30;
ShowMessage(Format('%s a %d ans', [Personne.Nom, Personne.Age]));
// Libération automatique
end;Delphi permet de déléguer l'implémentation d'une interface à un champ avec le mot-clé implements :
type
ICalculatrice = interface
['{5A6B7C8D-9E0F-1A2B-3C4D-5E6F7A8B9C0D}']
function Additionner(A, B: Integer): Integer;
function Soustraire(A, B: Integer): Integer;
end;
TMoteurCalcul = class(TInterfacedObject, ICalculatrice)
public
function Additionner(A, B: Integer): Integer;
function Soustraire(A, B: Integer): Integer;
end;
// Classe qui délègue l'interface
TCalculatriceScientifique = class(TInterfacedObject, ICalculatrice)
private
FMoteur: ICalculatrice; // Délégation à ce champ
public
constructor Create;
property Moteur: ICalculatrice read FMoteur implements ICalculatrice;
end;
function TMoteurCalcul.Additionner(A, B: Integer): Integer;
begin
Result := A + B;
end;
function TMoteurCalcul.Soustraire(A, B: Integer): Integer;
begin
Result := A - B;
end;
constructor TCalculatriceScientifique.Create;
begin
inherited Create;
FMoteur := TMoteurCalcul.Create;
end;| Critère | Interface | Classe abstraite |
|---|---|---|
| Héritage multiple | ✅ Oui | ❌ Non (héritage simple uniquement) |
| Implémentation | ❌ Aucune | ✅ Peut avoir des méthodes implémentées |
| Champs/données | ❌ Non | ✅ Oui |
| Gestion mémoire | ✅ Automatique | ❌ Manuelle (Free) |
| Flexibilité | ✅ Plus flexible | |
| Performance | ✅ Plus rapide |
- Vous avez besoin d'implémenter plusieurs "contrats"
- Vous voulez la gestion mémoire automatique
- Vous créez un système de plugins
- Vous voulez maximiser la flexibilité
- Vous voulez partager du code commun entre classes
- Vous avez des données à stocker
- L'héritage simple suffit
- La performance est critique
type
// Interface de notification
INotification = interface
['{9B8C7D6E-5F4A-3B2C-1D0E-9F8A7B6C5D4E}']
procedure Envoyer(const AMessage: string);
function EstDisponible: Boolean;
end;
// Notification par email
TNotificationEmail = class(TInterfacedObject, INotification)
private
FAdresseEmail: string;
public
constructor Create(const AEmail: string);
procedure Envoyer(const AMessage: string);
function EstDisponible: Boolean;
end;
// Notification par SMS
TNotificationSMS = class(TInterfacedObject, INotification)
private
FNumeroTelephone: string;
public
constructor Create(const ANumero: string);
procedure Envoyer(const AMessage: string);
function EstDisponible: Boolean;
end;
// Notification push
TNotificationPush = class(TInterfacedObject, INotification)
private
FDeviceToken: string;
public
constructor Create(const AToken: string);
procedure Envoyer(const AMessage: string);
function EstDisponible: Boolean;
end;
// Gestionnaire de notifications
TGestionnaireNotifications = class
private
FNotifications: TList<INotification>;
public
constructor Create;
destructor Destroy; override;
procedure AjouterCanal(Notification: INotification);
procedure DiffuserMessage(const AMessage: string);
end;
// Implémentation TNotificationEmail
constructor TNotificationEmail.Create(const AEmail: string);
begin
inherited Create;
FAdresseEmail := AEmail;
end;
procedure TNotificationEmail.Envoyer(const AMessage: string);
begin
ShowMessage(Format('Email envoyé à %s : %s', [FAdresseEmail, AMessage]));
end;
function TNotificationEmail.EstDisponible: Boolean;
begin
Result := FAdresseEmail <> '';
end;
// Implémentation TNotificationSMS
constructor TNotificationSMS.Create(const ANumero: string);
begin
inherited Create;
FNumeroTelephone := ANumero;
end;
procedure TNotificationSMS.Envoyer(const AMessage: string);
begin
ShowMessage(Format('SMS envoyé au %s : %s', [FNumeroTelephone, AMessage]));
end;
function TNotificationSMS.EstDisponible: Boolean;
begin
Result := FNumeroTelephone <> '';
end;
// Implémentation TNotificationPush
constructor TNotificationPush.Create(const AToken: string);
begin
inherited Create;
FDeviceToken := AToken;
end;
procedure TNotificationPush.Envoyer(const AMessage: string);
begin
ShowMessage(Format('Notification push envoyée : %s', [AMessage]));
end;
function TNotificationPush.EstDisponible: Boolean;
begin
Result := FDeviceToken <> '';
end;
// Implémentation TGestionnaireNotifications
constructor TGestionnaireNotifications.Create;
begin
inherited Create;
FNotifications := TList<INotification>.Create;
end;
destructor TGestionnaireNotifications.Destroy;
begin
FNotifications.Free;
inherited Destroy;
end;
procedure TGestionnaireNotifications.AjouterCanal(Notification: INotification);
begin
FNotifications.Add(Notification);
end;
procedure TGestionnaireNotifications.DiffuserMessage(const AMessage: string);
var
Notification: INotification;
begin
for Notification in FNotifications do
begin
if Notification.EstDisponible then
Notification.Envoyer(AMessage);
end;
end;var
Gestionnaire: TGestionnaireNotifications;
begin
Gestionnaire := TGestionnaireNotifications.Create;
try
// Ajouter différents canaux de notification
Gestionnaire.AjouterCanal(TNotificationEmail.Create('user@example.com'));
Gestionnaire.AjouterCanal(TNotificationSMS.Create('+33612345678'));
Gestionnaire.AjouterCanal(TNotificationPush.Create('device-token-123'));
// Diffuser un message sur tous les canaux
Gestionnaire.DiffuserMessage('Votre commande a été expédiée !');
finally
Gestionnaire.Free;
end;
end;// ✅ Bon
IDessinable, IComparable, INotification
// ❌ Mauvais
Dessinable, Comparable, Notification// ✅ Bon
IMonInterface = interface
['{3B8F9C12-5E4D-4A3B-9F2E-1C8D5A7B3F9E}']
...
end;
// ⚠️ Fonctionne mais déconseillé
IMonInterface = interface
...
end;// ✅ Bon
TMaClasse = class(TInterfacedObject, IMonInterface)
// ⚠️ Possible mais nécessite une gestion manuelle
TMaClasse = class(TObject, IMonInterface)// ✅ Bon - interfaces simples et spécifiques
ILisible = interface
procedure Lire;
end;
IEcrivable = interface
procedure Ecrire;
end;
// ❌ Éviter - interface trop large
IFichier = interface
procedure Lire;
procedure Ecrire;
procedure Copier;
procedure Deplacer;
procedure Supprimer;
procedure Renommer;
// ... trop de responsabilités
end;// ✅ Bon - dépend d'une interface
procedure TraiterDonnees(Source: ISourceDonnees);
// ⚠️ Moins flexible - dépend d'une classe concrète
procedure TraiterDonnees(Source: TBaseDonnees);-
Interface = contrat définissant ce qu'une classe doit faire
- Syntaxe :
interfaceavec un GUID - Nom conventionnel : préfixe
I - Pas d'implémentation, uniquement des signatures
- Syntaxe :
-
Implémentation
- Une classe peut implémenter plusieurs interfaces
- Hériter de
TInterfacedObjectpour la gestion mémoire automatique - Toutes les méthodes de l'interface doivent être implémentées
-
Avantages
- Héritage multiple
- Gestion mémoire automatique
- Flexibilité et découplage
- Parfait pour les systèmes de plugins
-
Opérateurs
Supports: vérifier si un objet implémente une interfaceas: convertir vers une interfaceimplements: déléguer l'implémentation
-
Utilisation
- Systèmes de plugins
- Injection de dépendances
- Architecture découplée
- Contrats entre modules
Les interfaces sont un outil puissant pour créer du code flexible, testable et maintenable. Elles favorisent le principe de programmation "contre une abstraction" plutôt que "contre une implémentation".
⏭️ Généricité