Interface vs. abstrakte Klasse
Teil des Abschnitts Objektorientierte Programmierung der C#-Journey von Coddy. Lektion 26 von 70.
Sowohl Schnittstellen als auch abstrakte Klassen ermöglichen es dir, Verträge zu definieren, denen andere Klassen folgen müssen. Sie dienen jedoch unterschiedlichen Zwecken und verfügen über unterschiedliche Fähigkeiten.
Eine Schnittstelle definiert, was eine Klasse tun muss, ohne eine Implementierung vorzugeben. Sie 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 wichtigsten Unterschiede betreffen Vererbungsregeln und Zweck.
Eine Klasse kann mehrere Schnittstellen implementieren, aber nur von einer abstrakten Klasse erben.
Eine Klasse kann auch von einer abstrakten Klasse erben und gleichzeitig Schnittstellen implementieren: Nach dem Doppelpunkt kommt zuerst die Basisklasse, dann die Schnittstellen, durch Kommas getrennt:
public class Airplane : Vehicle, IFlyable
{
public override void Drive()
{
Console.WriteLine("Taxiing to the runway");
}
public void Fly()
{
Console.WriteLine("Flying above the clouds");
}
}Verwende ein Interface, wenn nicht verwandte 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 sich einen Mechanismus zum Starten des Motors teilen).
Abstrakte Klassen können Konstruktoren, Felder und Zugriffsmodifizierer für Member haben. Bei Interfaces war das traditionell nicht möglich, obwohl modernes C# Standardimplementierungen erlaubt.
Im Zweifel solltest du aus Gründen der Flexibilität Schnittstellen bevorzugen – eine abstrakte Klasse kannst du später jederzeit hinzufügen, falls eine gemeinsame Implementierung erforderlich wird.
Aufgabe
EinfachLass uns ein Mediaplayer-System entwickeln, das zeigt, wann Schnittstellen und wann abstrakte Klassen verwendet werden sollten. Du entwirfst eine Lösung, bei der nicht verwandte Geräte über eine Schnittstelle ein gemeinsames 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 – egal, ob es sich um ein Telefon, einen Fernseher oder einen eigenständigen Musikplayer handelt. Sie sollte eine einzelne MethodePlay()deklarieren, die einen String zurückgibt.MediaFile.cs: Definiere eine abstrakte Klasse namensMediaFileim NamespaceMedia. Sie bildet die gemeinsame Grundlage für alle Mediendateien. Sie sollte eine EigenschaftTitle(string) und eine EigenschaftDuration(int, für die Dauer in Sekunden) haben. Füge einen Konstruktor hinzu, der beide Werte festlegt. Füge 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 sollte Titel, Dauer und Interpret entgegennehmen. ÜberschreibeGetType(), sodass die Methode"Audio"zurückgibt. ImplementierePlay()so, dass die Methode"Playing audio: {Title} by {Artist}"zurückgibt.Program.cs: Erstelle in deiner Hauptdatei mithilfe der Eingabewerte einAudioFile. Zeige beide Aspekte der Klasse: RufeGetInfo()undGetType()auf (von der abstrakten Klasse geerbt) und rufe anschließendPlay()auf (von der Schnittstelle). Speichere die Audiodatei in einerIPlayable-Variable und rufe erneutPlay()auf, um den Polymorphismus über eine Schnittstelle zu zeigen.
Du erhältst drei Eingaben:
- Titel des Songs
- 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: Die Klasse erbt die gemeinsame Implementierung (GetInfo()) von der abstrakten Klasse und erfüllt zugleich den Vertrag von IPlayable. Die abstrakte Klasse ermöglicht die Wiederverwendung von Code für verwandte Medientypen, während die Schnittstelle es jeder beliebigen, nicht verwandten Klasse ermöglicht, „abspielbar“ zu sein, ohne Teil derselben Vererbungshierarchie zu sein.
Probier es selbst
using System;
using Media;
class Program
{
public static void Main(string[] args)
{
// Eingabewerte einlesen
string title = Console.ReadLine();
int duration = Convert.ToInt32(Console.ReadLine());
string artist = Console.ReadLine();
// TODO: Eine AudioFile mit den Eingabewerten erstellen
// TODO: Info: ausgeben, gefolgt vom Ergebnis von GetInfo()
// TODO: Type: ausgeben, gefolgt vom Ergebnis von GetType()
// TODO: Direct: ausgeben, gefolgt vom Ergebnis von Play()
// TODO: Die Audiodatei in einer IPlayable-Variablen speichern
// TODO: Via Interface: ausgeben, gefolgt vom Ergebnis von Play() aus der IPlayable-Variablen
}
}
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