Explizite Interfaces
Teil des Abschnitts Objektorientierte Programmierung der C#-Journey von Coddy — Lektion 28 von 70.
Wenn eine Klasse mehrere Schnittstellen implementiert, die Member mit demselben Namen haben, tritt ein Namenskonflikt auf. Die explizite Schnittstellenimplementierung löst dies, indem sie es Ihnen ermöglicht, separate Implementierungen für jede Schnittstelle bereitzustellen.
Bei der expliziten Implementierung stellen Sie dem Membernamen den Schnittstellennamen voran und lassen den Zugriffsmodifizierer weg:
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");
}
}Explizit implementierte Member können nur über eine Variable des Schnittstellentyps aufgerufen werden, nicht über die Klasse selbst:
var adapter = new UniversalAdapter();
// adapter.Connect(); // Kompiliert nicht!
IAmericanPlug american = adapter;
american.Connect(); // Verbunden mit 120V
IEuropeanPlug european = adapter;
european.Connect(); // Verbunden mit 220VDiese Technik ist auch nützlich, wenn Sie Interface-Member vor der öffentlichen API der Klasse verbergen möchten. Wenn eine Methode nur dann sinnvoll ist, wenn das Objekt als ein bestimmtes Interface behandelt wird, hält die explizite Implementierung die Oberfläche Ihrer Klasse sauber, während der Vertrag dennoch erfüllt wird.
Aufgabe
EinfachLassen Sie uns ein Bericht-Generator-System erstellen, das demonstriert, wie die explizite Schnittstellenimplementierung es einer einzelnen Klasse ermöglicht, unterschiedliche Verhaltensweisen bereitzustellen, je nachdem, welche Schnittstelle verwendet wird. Sie werden ein Dokument erstellen, das für die Anzeige auf dem Bildschirm anders formatiert werden kann als für den Druck.
Sie werden Ihren Code in vier Dateien organisieren:
IDisplayable.cs: Definieren Sie eine Schnittstelle namensIDisplayableim NamespaceReports. Diese Schnittstelle repräsentiert Inhalte, die auf dem Bildschirm angezeigt werden können. Sie sollte eine MethodeRender()deklarieren, die einen String zurückgibt.IPrintable.cs: Definieren Sie eine Schnittstelle namensIPrintableim NamespaceReports. Diese Schnittstelle repräsentiert Inhalte, die an einen Drucker gesendet werden können. Sie sollte ebenfalls eine MethodeRender()deklarieren, die einen String zurückgibt – beachten Sie, dass dies eine Namenskollision mitIDisplayableverursacht!Report.cs: Definieren Sie eine KlasseReportim NamespaceReports, die sowohlIDisplayableals auchIPrintableimplementiert. Der Bericht sollte eine EigenschaftTitle(String) und eine EigenschaftContent(String) haben, mit einem Konstruktor, der beide Werte setzt. Verwenden Sie die explizite Schnittstellenimplementierung, um unterschiedlicheRender()-Verhaltensweisen bereitzustellen:- Bei der Darstellung für die Anzeige:
"[SCREEN] {Title}: {Content}" - Bei der Darstellung für den Druck:
"[PRINT] {Title} | {Content}"
- Bei der Darstellung für die Anzeige:
Program.cs: Erstellen Sie in Ihrer Hauptdatei einReport-Objekt unter Verwendung von Eingabewerten. Da auf explizite Implementierungen nur über Schnittstellenvariablen zugegriffen werden kann, speichern Sie denselben Bericht sowohl in einerIDisplayable-Variable als auch in einerIPrintable-Variable. Rufen Sie dannRender()für beide auf, um zu zeigen, wie dasselbe Objekt je nach verwendeter Schnittstelle unterschiedliche Ausgaben erzeugt.
Sie erhalten zwei Eingaben:
- Berichtstitel
- Berichtsinhalt
Geben Sie die Ausgabe in diesem Format aus:
Display version:
{Render() via IDisplayable}
Print version:
{Render() via IPrintable}Wenn die Eingaben beispielsweise Sales Report und Q4 revenue increased by 15% sind, sollte die Ausgabe wie folgt aussehen:
Display version:
[SCREEN] Sales Report: Q4 revenue increased by 15%
Print version:
[PRINT] Sales Report | Q4 revenue increased by 15%Beachten Sie, wie sich dasselbe Report-Objekt unterschiedlich verhält, je nachdem, über welche Schnittstellenreferenz Sie darauf zugreifen. Dies ist die Stärke der expliziten Schnittstellenimplementierung – das Lösen von Namenskollisionen bei gleichzeitiger Bereitstellung von kontextgerechtem Verhalten!
Spickzettel
Wenn eine Klasse mehrere Schnittstellen implementiert, deren Member denselben Namen haben, können Sie die explizite Schnittstellenimplementierung verwenden, um separate Implementierungen für jede Schnittstelle bereitzustellen.
Um ein Schnittstellen-Member explizit zu implementieren, stellen Sie dem Member-Namen den Namen der Schnittstelle voran und lassen Sie den Zugriffsmodifizierer weg:
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");
}
}Auf explizit implementierte Member kann nur über eine Variable des Schnittstellentyps zugegriffen werden, nicht über die Klasse selbst:
var adapter = new UniversalAdapter();
// adapter.Connect(); // Kompiliert nicht!
IAmericanPlug american = adapter;
american.Connect(); // Connected to 120V
IEuropeanPlug european = adapter;
european.Connect(); // Connected to 220VDiese Technik ist nützlich für:
- Auflösen von Namenskollisionen zwischen Schnittstellen-Membern
- Verbergen von Schnittstellen-Membern vor der öffentlichen API der Klasse, wenn sie nur in einem bestimmten Schnittstellenkontext sinnvoll sind
Probier es selbst
using System;
using Reports;
class Program
{
public static void Main(string[] args)
{
// Eingabe lesen
string title = Console.ReadLine();
string content = Console.ReadLine();
// TODO: Erstelle ein Report-Objekt mit dem Titel und Inhalt
// TODO: Speichere den Bericht in einer IDisplayable-Variable
// TODO: Speichere denselben Bericht in einer IPrintable-Variable
// TODO: Gib die Anzeigeversion aus, indem Render() auf IDisplayable aufgerufen wird
Console.WriteLine("Display version:");
// Gib das Render-Ergebnis hier aus
// TODO: Gib die Druckversion aus, indem Render() auf IPrintable aufgerufen wird
Console.WriteLine("Print version:");
// Gib das Render-Ergebnis hier aus
}
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der OOP
Externe DateienNamespaces & DirektivenEinführung in Klassen & ObjekteDas 'this'-SchlüsselwortMethoden und ParameterFelder vs. EigenschaftenKonstruktorenObjekt-InitialisiererZusammenfassung - Einfacher Taschenrechner4Vererbung
Grundlagen der Vererbung (:) SyntaxDas 'base'-SchlüsselwortVirtual & Override SchlüsselwörterVersiegelte KlassenDie 'object'-BasisklasseZusammenfassung – Mitarbeiter-Hierarchie7Fortgeschrittene Funktionen
OperatorüberladungIndexer (this[])ToString() überschreibenErweiterungsmethodenZusammenfassung - Benutzerdefinierte Liste2Eigenschaften & Statische Member
Automatisch implementierte EigenschaftenRead-Only- & Write-Only-EigenschaftenStatische Felder & MethodenStatische KlassenExpression-Bodied Member5Polymorphismus & Interfaces
Compile- vs. Runtime-PolymorphismusInterface vs. abstrakte KlasseMehrfache InterfacesExplizite InterfacesUpcasting & DowncastingRecap – Shape Calculator3Klassenarchitektur
Instanz- vs. statische Daten'readonly' & 'const' SchlüsselwörterBacking FieldsZusammenfassung - Bankkonto-Manager6Kapselung
ZugriffsmodifikatorenProperties zur KapselungImplementierung von Data HidingImmutability-MusterRückblick – Studierendendaten