Interface vs. abstrakte Klasse
Teil des Abschnitts Objektorientierte Programmierung der C#-Journey von Coddy — Lektion 26 von 70.
Sowohl Interfaces als auch abstrakte Klassen ermöglichen es Ihnen, Verträge zu definieren, denen andere Klassen folgen müssen, aber sie dienen unterschiedlichen Zwecken und haben unterschiedliche Fähigkeiten.
Ein Interface definiert, was eine Klasse tun muss, ohne jegliche Implementierung. Es ist ein reiner Vertrag:
public interface IFlyable
{
void Fly();
}
public class Bird : IFlyable
{
public void Fly()
{
Console.WriteLine("Flapping wings");
}
}Eine abstrakte Klasse kann sowohl einen Vertrag als auch eine gemeinsame Implementierung bereitstellen. Sie ist ein teilweiser Bauplan:
public abstract class Vehicle
{
public string Brand { get; set; }
public void StartEngine()
{
Console.WriteLine("Engine started");
}
public abstract void Drive(); // Muss implementiert werden
}
public class Car : Vehicle
{
public override void Drive()
{
Console.WriteLine("Driving on roads");
}
}Die wesentlichen Unterschiede liegen in den Vererbungsregeln und dem Verwendungszweck.
Eine class kann mehrere interfaces implementieren, aber nur von einer abstract class erben.
Verwenden Sie ein Interface, wenn nicht verwandte Klassen gemeinsames Verhalten teilen müssen (wie IFlyable für Vögel und Flugzeuge). Verwenden Sie eine abstrakte Klasse, wenn verwandte Klassen gemeinsamen Code und Zustand teilen (wie Fahrzeuge, die einen Motorstartmechanismus teilen).
Abstrakte Klassen können Konstruktoren, Felder und Zugriffsmodifikatoren für Member haben. Interfaces konnten dies traditionell nicht, obwohl das moderne C# Standardimplementierungen erlaubt.
Im Zweifelsfall bevorzugen Sie Interfaces für mehr Flexibilität – Sie können später jederzeit eine abstrakte Klasse hinzufügen, falls eine gemeinsame Implementierung notwendig wird.
Aufgabe
EinfachLassen Sie uns ein Mediaplayer-System bauen, das demonstriert, wann Schnittstellen (Interfaces) gegenüber abstrakten Klassen zu bevorzugen sind. Sie werden ein Design entwerfen, bei dem nicht verwandte Geräte das Wiedergabeverhalten über ein Interface teilen, während verwandte Medientypen eine gemeinsame Implementierung über eine abstrakte Klasse teilen.
Sie werden Ihren Code über vier Dateien organisieren:
IPlayable.cs: Definieren Sie ein Interface namensIPlayableim NamespaceMedia. Dieses Interface repräsentiert den Vertrag für alles, was Medien abspielen kann – egal ob es sich um ein Telefon, einen Fernseher oder einen dedizierten Musikplayer handelt. Es sollte eine einzige MethodePlay()deklarieren, die einen String zurückgibt.MediaFile.cs: Definieren Sie eine abstrakte Klasse namensMediaFileim NamespaceMedia. Diese stellt die gemeinsame Basis für alle Mediendateien dar. Sie sollte eine EigenschaftTitle(String) und eine EigenschaftDuration(int, repräsentiert Sekunden) haben. Fügen Sie einen Konstruktor hinzu, der beide Werte setzt. Fügen Sie eine konkrete Methode namensGetInfo()hinzu, die"{Title} ({Duration}s)"zurückgibt. Deklarieren Sie außerdem eine abstrakte MethodeGetType(), die einen String zurückgibt – jeder Medientyp wird definieren, um welche Art von Datei es sich handelt.AudioFile.cs: Definieren Sie eine KlasseAudioFileim NamespaceMedia, die vonMediaFileerbt undIPlayableimplementiert. Fügen Sie eine EigenschaftArtist(String) hinzu. Der Konstruktor sollte Titel, Dauer und Künstler akzeptieren. Überschreiben SieGetType(), um"Audio"zurückzugeben. Implementieren SiePlay(), um"Playing audio: {Title} by {Artist}"zurückzugeben.Program.cs: Erstellen Sie in Ihrer Hauptdatei einAudioFile-Objekt unter Verwendung von Eingabewerten. Demonstrieren Sie beide Aspekte der Klasse: Rufen SieGetInfo()undGetType()auf (geerbt von der abstrakten Klasse), und rufen Sie dannPlay()auf (vom Interface). Speichern Sie die Audiodatei in einerIPlayable-Variable und rufen SiePlay()erneut auf, um Interface-Polymorphie zu zeigen.
Sie erhalten drei Eingaben:
- Songtitel
- Dauer in Sekunden
- Name des Künstlers
Geben Sie die Ausgabe in diesem Format aus:
Info: {GetInfo() result}
Type: {GetType() result}
Direct: {Play() result}
Via Interface: {Play() result from IPlayable variable}Wenn die Eingaben beispielsweise Bohemian Rhapsody, 354 und Queen sind, sollte die Ausgabe wie folgt aussehen:
Info: Bohemian Rhapsody (354s)
Type: Audio
Direct: Playing audio: Bohemian Rhapsody by Queen
Via Interface: Playing audio: Bohemian Rhapsody by QueenBeachten Sie, wie AudioFile von beiden Ansätzen profitiert: Es erbt die gemeinsame Implementierung (GetInfo()) von der abstrakten Klasse und erfüllt gleichzeitig den IPlayable-Vertrag. Die abstrakte Klasse ermöglicht die Wiederverwendung von Code für verwandte Medientypen, während das Interface es jeder nicht verwandten Klasse ermöglicht, "abspielbar" zu sein, ohne eine Vererbungshierarchie teilen zu müssen.
Spickzettel
Ein Interface definiert einen Vertrag, dem Klassen folgen müssen, ohne jegliche Implementierung:
public interface IFlyable
{
void Fly();
}
public class Bird : IFlyable
{
public void Fly()
{
Console.WriteLine("Flapping wings");
}
}Eine abstrakte Klasse bietet sowohl einen Vertrag als auch eine gemeinsame Implementierung:
public abstract class Vehicle
{
public string Brand { get; set; }
public void StartEngine()
{
Console.WriteLine("Engine started");
}
public abstract void Drive(); // Muss implementiert werden
}
public class Car : Vehicle
{
public override void Drive()
{
Console.WriteLine("Driving on roads");
}
}Wesentliche Unterschiede:
- Eine Klasse kann mehrere Interfaces implementieren, aber nur von einer abstrakten Klasse erben
- Verwenden Sie Interfaces, wenn nicht verwandte Klassen gemeinsames Verhalten teilen müssen
- Verwenden Sie abstrakte Klassen, wenn verwandte Klassen gemeinsamen Code und Zustand teilen
- Abstrakte Klassen können Konstruktoren, Felder und Zugriffsmodifikatoren haben
- Bevorzugen Sie Interfaces für mehr Flexibilität
Probier es selbst
using System;
using Media;
class Program
{
public static void Main(string[] args)
{
// Eingabewerte lesen
string title = Console.ReadLine();
int duration = Convert.ToInt32(Console.ReadLine());
string artist = Console.ReadLine();
// TODO: Erstelle ein AudioFile unter Verwendung der Eingabewerte
// TODO: Gib Info: gefolgt vom Ergebnis von GetInfo() aus
// TODO: Gib Type: gefolgt vom Ergebnis von GetType() aus
// TODO: Gib Direct: gefolgt vom Ergebnis von Play() aus
// TODO: Speichere die Audiodatei in einer IPlayable-Variable
// TODO: Gib Via Interface: gefolgt vom Ergebnis von Play() aus der IPlayable-Variable 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