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 dir, Verträge zu definieren, die andere Klassen befolgen müssen, aber sie dienen unterschiedlichen Zwecken und verfügen über unterschiedliche Fähigkeiten.
Ein Interface definiert, was eine Klasse tun muss, ohne eine Implementierung vorzugeben. 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 unvollständiger 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 ergeben sich aus den Vererbungsregeln und dem Zweck.
Eine Klasse kann mehrere Schnittstellen implementieren, aber nur von einer abstrakten Klasse erben.
Verwende eine Schnittstelle, wenn unabhängige Klassen Verhalten gemeinsam nutzen müssen (wie IFlyable für Vögel und Flugzeuge). Verwende eine abstrakte Klasse, wenn verwandte Klassen gemeinsamen Code und Zustand teilen (wie Fahrzeuge, die einen gemeinsamen Mechanismus zum Starten des Motors verwenden).
Abstrakte Klassen können Konstruktoren, Felder und Zugriffsmodifizierer für Member enthalten. Schnittstellen konnten dies traditionell nicht, obwohl modernes C# Standardimplementierungen erlaubt.
Im Zweifelsfall solltest du aus Gründen der Flexibilität Schnittstellen bevorzugen – du kannst später jederzeit eine abstrakte Klasse hinzufügen, falls eine gemeinsame Implementierung erforderlich wird.
Aufgabe
EinfachErstelle ein Mediaplayer-System, das veranschaulicht, wann Schnittstellen und wann abstrakte Klassen verwendet werden. Du entwirfst ein System, in dem nicht verwandte Geräte über eine Schnittstelle Wiedergabeverhalten teilen, während verwandte Medientypen über eine abstrakte Klasse eine gemeinsame Implementierung nutzen.
Du organisierst deinen Code auf vier Dateien:
IPlayable.cs: Definiere eine Schnittstelle namensIPlayableim NamespaceMedia. Diese Schnittstelle stellt den Vertrag für alles dar, was Medien abspielen kann – unabhängig davon, ob es sich um ein Telefon, einen Fernseher oder einen dedizierten Musikplayer handelt. Sie soll eine einzige MethodePlay()deklarieren, die einen String zurückgibt.MediaFile.cs: Definiere eine abstrakte Klasse namensMediaFileim NamespaceMedia. Sie stellt die gemeinsame Grundlage für alle Mediendateien dar. Sie soll eine EigenschaftTitle(string) und eine EigenschaftDuration(int, die Sekunden repräsentiert) besitzen. Füge einen Konstruktor hinzu, der beide Werte setzt. Füge außerdem eine konkrete Methode namensGetInfo()hinzu, die"{Title} ({Duration}s)"zurückgibt. Deklariere außerdem eine abstrakte MethodeGetType(), die einen String zurückgibt – jeder Medientyp legt fest, um welche Art von Datei es sich handelt.AudioFile.cs: Definiere eine KlasseAudioFileim NamespaceMedia, die vonMediaFileerbt undIPlayableimplementiert. Füge eine EigenschaftArtist(string) hinzu. Der Konstruktor soll Titel, Dauer und Interpret entgegennehmen. ÜberschreibeGetType(), sodass"Audio"zurückgegeben wird. ImplementierePlay(), sodass"Playing audio: {Title} by {Artist}"zurückgegeben wird.Program.cs: Erstelle in deiner Hauptdatei anhand von Eingabewerten einAudioFile. Veranschauliche beide Aspekte der Klasse: RufeGetInfo()undGetType()auf (von der abstrakten Klasse geerbt), und rufe anschließendPlay()auf (aus der Schnittstelle). Speichere die Audiodatei in einerIPlayable-Variable und rufePlay()erneut auf, um den Polymorphismus der Schnittstelle zu zeigen.
Du erhältst drei Eingaben:
- Songtitel
- Dauer in Sekunden
- Name des Interpreten
Gib 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 lauten, 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 QueenBeachte, wie AudioFile von beiden Ansätzen profitiert: Es erbt eine gemeinsame Implementierung (GetInfo()) von der abstrakten Klasse und erfüllt gleichzeitig den Vertrag von IPlayable. Die abstrakte Klasse ermöglicht die Wiederverwendung von Code für verwandte Medientypen, während die Schnittstelle es jeder nicht verwandten Klasse ermöglicht, „abspielbar“ zu sein, ohne eine gemeinsame Vererbungshierarchie zu benötigen.
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 eine AudioFile mit den Eingabewerten
// 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. EigenschaftenKonstruktorenObjektinitialisiererWiederholung – Einfacher Taschenrechner4Vererbung
Grundlegende Vererbungs-(:)-SyntaxDas „base“-Schlüsselwort„virtual“- und „override“-SchlüsselwörterVersiegelte KlassenDie „object“-BasisklasseWiederholung – Mitarbeiterhierarchie7Erweiterte Funktionen
Überladen von OperatorenIndexer (this[])ToString()-ÜberschreibungErweiterungsmethodenRückblick – Benutzerdefinierte Liste10Entwurfsmuster Teil 1
Einführung in EntwurfsmusterThread-sicheres SingletonFactory-MusterObserver-Muster (Events)Strategy-Muster13Abschließende Herausforderungen
E-Learning-PlattformKomponente für SpielfigurenFahrzeugvermietungsdienst2Eigenschaften & statische Member
Automatisch implementierte EigenschaftenNur lesbare/schreibbare EigenschaftenStatische Felder & MethodenStatische KlassenMember mit Ausdruckskörpern5Polymorphie & Interfaces
Compile- vs. Laufzeit-PolymorphieInterface vs. abstrakte KlasseMehrere InterfacesExplizite InterfacesUpcasting & DowncastingWiederholung – Formenrechner3Klassenarchitektur
Instanz- vs. statische DatenSchlüsselwörter 'readonly' & 'const'Backing FieldsRückblick – Bankkontenverwaltung6Kapselung
ZugriffsmodifikatorenEigenschaften zur KapselungImplementierung der DatenkapselungMuster für UnveränderlichkeitRückblick – SchülerdatenÜbe selbstständig: Online-C#-Compiler