Interfaces Explicites
Fait partie de la section Programmation Orientée Objet du Journey C# de Coddy — leçon 28 sur 70.
Lorsqu'une classe implémente plusieurs interfaces qui possèdent des membres portant le même nom, vous êtes confronté à une collision de noms. L'implémentation explicite d'interface résout ce problème en vous permettant de fournir des implémentations distinctes pour chaque interface.
Avec une implémentation explicite, vous préfixez le nom du membre par le nom de l'interface et omettez le modificateur d'accès :
public interface IAmericanPlug
{
void Connect();
}
public interface IEuropeanPlug
{
void Connect();
}
public class UniversalAdapter : IAmericanPlug, IEuropeanPlug
{
void IAmericanPlug.Connect()
{
Console.WriteLine("Connected to 120V");
}
void IEuropeanPlug.Connect()
{
Console.WriteLine("Connected to 220V");
}
}Les membres implémentés de manière explicite ne peuvent être accédés que via une variable du type de l'interface, et non via la classe elle-même :
var adapter = new UniversalAdapter();
// adapter.Connect(); // Ne compilera pas !
IAmericanPlug american = adapter;
american.Connect(); // Connecté au 120V
IEuropeanPlug european = adapter;
european.Connect(); // Connecté au 220VCette technique est également utile lorsque vous souhaitez masquer les membres de l'interface de l'API publique de la classe. Si une méthode n'a de sens que lorsque l'on traite l'objet comme une interface spécifique, l'implémentation explicite permet de garder la surface d'exposition de votre classe propre tout en respectant le contrat.
Défi
FacileConstruisons un système de génération de rapports qui démontre comment l'implémentation explicite d'interface permet à une seule classe de fournir des comportements différents selon l'interface utilisée. Vous allez créer un document qui peut être formaté différemment pour l'affichage par rapport à l'impression.
Vous organiserez votre code sur quatre fichiers :
IDisplayable.cs: Définissez une interface nomméeIDisplayabledans l'espace de nomsReports. Cette interface représente un contenu qui peut être affiché à l'écran. Elle doit déclarer une méthodeRender()qui retourne une chaîne de caractères (string).IPrintable.cs: Définissez une interface nomméeIPrintabledans l'espace de nomsReports. Cette interface représente un contenu qui peut être envoyé à une imprimante. Elle doit également déclarer une méthodeRender()qui retourne une chaîne de caractères — remarquez que cela crée une collision de noms avecIDisplayable!Report.cs: Définissez une classeReportdans l'espace de nomsReportsqui implémente à la foisIDisplayableetIPrintable. Le rapport doit avoir une propriétéTitle(string) et une propriétéContent(string), avec un constructeur qui définit ces deux valeurs. Utilisez l'implémentation explicite d'interface pour fournir différents comportements àRender():- Lors du rendu pour l'affichage :
"[SCREEN] {Title}: {Content}" - Lors du rendu pour l'impression :
"[PRINT] {Title} | {Content}"
- Lors du rendu pour l'affichage :
Program.cs: Dans votre fichier principal, créez unReporten utilisant les valeurs d'entrée. Étant donné que les implémentations explicites ne sont accessibles que via des variables d'interface, stockez le même rapport dans une variableIDisplayableet une variableIPrintable, puis appelezRender()sur chacune pour montrer comment le même objet produit des sorties différentes selon l'interface que vous utilisez.
Vous recevrez deux entrées :
- Le titre du rapport
- Le contenu du rapport
Affichez la sortie dans ce format :
Display version:
{Render() via IDisplayable}
Print version:
{Render() via IPrintable}Par exemple, si les entrées sont Sales Report et Q4 revenue increased by 15%, la sortie devrait être :
Display version:
[SCREEN] Sales Report: Q4 revenue increased by 15%
Print version:
[PRINT] Sales Report | Q4 revenue increased by 15%Remarquez comment le même objet Report se comporte différemment selon la référence d'interface que vous utilisez pour y accéder. C'est toute la puissance de l'implémentation explicite d'interface : résoudre les collisions de noms tout en fournissant un comportement approprié au contexte !
Aide-mémoire
Lorsqu'une classe implémente plusieurs interfaces avec des membres ayant le même nom, vous pouvez utiliser l'implémentation d'interface explicite pour fournir des implémentations distinctes pour chaque interface.
Pour implémenter explicitement un membre d'interface, préfixez le nom du membre avec le nom de l'interface et omettez le modificateur d'accès :
public interface IAmericanPlug
{
void Connect();
}
public interface IEuropeanPlug
{
void Connect();
}
public class UniversalAdapter : IAmericanPlug, IEuropeanPlug
{
void IAmericanPlug.Connect()
{
Console.WriteLine("Connected to 120V");
}
void IEuropeanPlug.Connect()
{
Console.WriteLine("Connected to 220V");
}
}Les membres implémentés explicitement ne peuvent être accédés que via une variable du type de l'interface, et non via la classe elle-même :
var adapter = new UniversalAdapter();
// adapter.Connect(); // Won't compile!
IAmericanPlug american = adapter;
american.Connect(); // Connected to 120V
IEuropeanPlug european = adapter;
european.Connect(); // Connected to 220VCette technique est utile pour :
- Résoudre les collisions de noms entre les membres d'interface
- Masquer les membres d'interface de l'API publique de la classe lorsqu'ils n'ont de sens que dans le contexte d'une interface spécifique
Essayez vous-même
using System;
using Reports;
class Program
{
public static void Main(string[] args)
{
// Lire l'entrée
string title = Console.ReadLine();
string content = Console.ReadLine();
// TODO: Créer un objet Report avec le titre et le contenu
// TODO: Stocker le rapport dans une variable IDisplayable
// TODO: Stocker le même rapport dans une variable IPrintable
// TODO: Afficher la version d'affichage en appelant Render() sur IDisplayable
Console.WriteLine("Display version:");
// Afficher le résultat du rendu ici
// TODO: Afficher la version d'impression en appelant Render() sur IPrintable
Console.WriteLine("Print version:");
// Afficher le résultat du rendu ici
}
}
Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.
Toutes les leçons de Programmation Orientée Objet
1Fondamentaux de la POO
Fichiers externesEspaces de noms et directivesIntro aux classes et objetsLe mot-clé 'this'Méthodes et paramètresChamps vs PropriétésConstructeursInitialiseurs d'objetsRécapitulatif - Calculatrice simple4Héritage
Syntaxe de base de l'héritage (:)Le mot-clé 'base'Mots-clés Virtual & OverrideClasses scelléesLa classe de base 'object'Récapitulatif - Hiérarchie des employés7Fonctionnalités avancées
Surcharge d'opérateursIndexeurs (this[])Redéfinition de ToString()Méthodes d'extensionRécapitulatif - Liste personnalisée10Patrons de conception - Partie 1
Introduction aux patrons de conceptionSingleton Thread-SafePatron FabriquePatron Observateur (Événements)Patron Stratégie2Propriétés et membres statiques
Propriétés auto-implémentéesPropriétés en lecture/écriture seuleChamps et méthodes statiquesClasses statiquesMembres à corps d'expression5Polymorphisme & Interfaces
Polymorphisme : Compilation vs ExécutionInterface vs Classe AbstraiteInterfaces MultiplesInterfaces ExplicitesUpcasting & DowncastingRécapitulatif - Calculateur de Formes8Concepts avancés de la POO
Composition plutôt qu'héritageGénériques (Classes et Méthodes)Délégués et ÉvénementsAttributs et RéflexionIDisposable et l'instruction usingBases de l'Injection de Dépendances11Patrons de conception, Partie 2
Patron CommandePatron AdaptateurPatron DécorateurPatron Méthode TemplatePatron ÉtatPatron Composite3Architecture des classes
Données d'instance vs statiquesMots-clés 'readonly' & 'const'Champs de supportRécapitulatif - Gestionnaire de compte bancaire6Encapsulation
Modificateurs d'accèsPropriétés pour l'encapsulationImplémentation du masquage de donnéesPatterns d'immuabilitéRécapitulatif - Dossiers d'étudiants12Projet : Système de gestion de bibliothèque
Structure du projetModèles Livre et Utilisateur