1. Interfaces in der Business-Logik: Buttons und Aktionen
Das ist die abschließende Vorlesung zu Interfaces, deshalb gibt es hier viele praktische Beispiele, damit du besser verstehst, wie Interfaces in der Praxis genutzt werden.
Interfaces sind besonders wertvoll in großen Anwendungen. Deshalb findest du unten komplexere Fälle, manchmal sogar ein bisschen voraus zum Kursprogramm. Wenn’s dich interessiert – cool! Wenn nicht – geh einfach zum nächsten Level :P
Das einfachste Beispiel – Buttons und Klicks darauf
Wir entwickeln weiter unsere Lern-Konsolenanwendung – stell dir vor, wir haben ein simples System von UI-Elementen (zum Beispiel Buttons, Textfelder und Switches) im Konsolenmenü.
Wir wollen, dass manche Elemente auf Klicks reagieren (Buttons), andere aber nicht (zum Beispiel ein statischer Titel). Interfaces machen solche Szenarien elegant möglich.
Schritt 1: Interface erstellen
// Komponente, die man "klicken" kann:
public interface IClickable
{
void Click();
}
Schritt 2: Interface für verschiedene Elementtypen anwenden
// Basisklasse für alle Menü-Elemente
public class MenuItem
{
public string Title { get; set; }
public MenuItem(string title)
{
Title = title;
}
public virtual void Display()
{
Console.WriteLine(Title);
}
}
// Button: kann "geklickt" werden
public class Button : MenuItem, IClickable
{
public Button(string title) : base(title) {}
public void Click()
{
Console.WriteLine($"[Button] {Title} wurde geklickt!");
}
}
// Nur ein Label: kann nicht geklickt werden
public class Label : MenuItem
{
public Label(string title) : base(title) {}
}
Schritt 3: Polymorphismus des Interfaces nutzen
IClickable[] clickableItems = new IClickable[]
{
new Button("Speichern"),
new Button("Beenden")
// Label kann hier nicht hinzugefügt werden – ist kein IClickable!
};
foreach (var item in clickableItems)
{
item.Click();
}
Siehst du die Magie? Die Liste nimmt nur die, die “Click” können, das heißt, du bekommst keinen Laufzeitfehler, wenn du versuchst, auf ein ungeeignetes Objekt zu “klicken”.
2. Interfaces und das “Strategie”-Pattern: Algorithmus zur Laufzeit wählen
Angenommen, du schreibst eine App, in der man Reports auf verschiedene Arten speichern kann: in eine Datei, in eine Datenbank, in die “Cloud” oder vielleicht sogar per Telegram an deinen Chef (frag nicht, alles schon passiert).
Schritt 1: Aufgabe formulieren
Die Klasse ReportGenerator soll mit jeder Speicherart arbeiten können, ohne die Implementierungsdetails zu kennen.
Schritt 2: Strategie-Interface beschreiben
public interface IDataSaver
{
void Save(string reportData);
}
Schritt 3: Verschiedene Varianten implementieren
public class FileDataSaver : IDataSaver
{
public void Save(string reportData)
{
Console.WriteLine("[FileDataSaver] Speichere in Datei...\n" + reportData);
// Hier könnte Code für File.WriteAllText(...) stehen
}
}
public class DatabaseDataSaver : IDataSaver
{
public void Save(string reportData)
{
Console.WriteLine("[DatabaseDataSaver] Speichere in Datenbank...\n" + reportData);
// Hier könnte Code für Datenbank-Schreiben stehen
}
}
Schritt 4: Interface nutzen, um die Speicherstrategie auszutauschen
public class ReportGenerator
{
private readonly IDataSaver _dataSaver;
public ReportGenerator(IDataSaver dataSaver)
{
_dataSaver = dataSaver;
}
public void GenerateReport()
{
string report = "Das ist ein wichtiger Report!";
Console.WriteLine("Report wird generiert...");
_dataSaver.Save(report);
}
}
Demonstration der Flexibilität
// Du kannst die Speicherart austauschen, ohne ReportGenerator umzuschreiben!
IDataSaver fileSaver = new FileDataSaver();
ReportGenerator fileReport = new ReportGenerator(fileSaver);
fileReport.GenerateReport();
IDataSaver dbSaver = new DatabaseDataSaver();
ReportGenerator dbReport = new ReportGenerator(dbSaver);
dbReport.GenerateReport();
Praktischer Sinn: Wenn du morgen Reports in einen neuen, supermodernen Service schreiben willst, reicht es, eine neue Klasse zu schreiben, die das Interface implementiert, und das Objekt in deine Architektur einzufügen – ohne eine Zeile bestehenden Code zu ändern.
3. Interfaces in .NET: IDisposable und using
Eines der meistgenutzten Interfaces in .NET ist IDisposable. Das implementieren alle Klassen, die mit unmanaged Ressourcen arbeiten: Dateien, Streams, Netzwerkverbindungen.
Wozu braucht man IDisposable?
Wenn du mit einer Ressource arbeitest, die unbedingt freigegeben werden muss (zum Beispiel eine Datei schließen), implementierst du IDisposable und schreibst die Methode Dispose. So kannst du solche Objekte im using-Block verwenden, der garantiert, dass Dispose beim Verlassen des Blocks aufgerufen wird.
Beispiel: Dateizugriff simulieren
public class FakeFile : IDisposable
{
public string FileName { get; }
public FakeFile(string fileName)
{
FileName = fileName;
Console.WriteLine($"Datei geöffnet: {fileName}");
}
public void Dispose()
{
Console.WriteLine($"Datei geschlossen: {FileName}");
}
}
// Im Hauptprogramm:
using (var file = new FakeFile("bericht.txt"))
{
Console.WriteLine("Schreiben in Datei...");
// Datei wird nach using automatisch "geschlossen"
}
Ausgabe:
Datei geöffnet: bericht.txt
Schreiben in Datei...
Datei geschlossen: bericht.txt
Wirklich praktisch: Du vergisst nie, die Datei zu schließen – Interface und Sprach-Infrastruktur schützen dich.
4. Interfaces in Collections und LINQ
Wenn du mit Listen, Arrays, Dictionaries arbeitest, nutzt du schon Interfaces, auch wenn du nicht daran denkst.
List<int> list = new List<int> { 1, 2, 3 };
IEnumerable<int> enumerable = list; // alles ok!
// Jetzt kannst du die Elemente so durchgehen:
foreach(var x in enumerable)
{
Console.WriteLine(x);
}
Die meisten LINQ-Methoden arbeiten mit Collections über das Interface IEnumerable<T>. So kannst du Code schreiben, der nicht vom konkreten Collection-Typ abhängt.
Wozu das Ganze?
Du kannst List<T> durch T[], HashSet<T> oder sogar deine eigene Collection ersetzen – und dein Code läuft weiter!
5. Interfaces fürs Testen (Mock-Objekte)
Beim Testen ist es superwichtig, den Code von externen Abhängigkeiten zu isolieren: nicht in die echte Datenbank gehen, keine echten Dateien anfassen. Interfaces erlauben dir, Stubs (Mock-Objekte) zu nutzen und Code zu testen, ohne echte Datenbanken oder Dateien zu berühren.
public class FakeDataSaver : IDataSaver
{
public bool WasCalled { get; private set; } = false;
public void Save(string reportData)
{
WasCalled = true;
Console.WriteLine("Daten im Mock-Objekt gespeichert!");
}
}
// Im Test
FakeDataSaver saver = new FakeDataSaver();
ReportGenerator generator = new ReportGenerator(saver);
generator.GenerateReport();
Console.WriteLine($"Wurde Save aufgerufen? {saver.WasCalled}");
Ergebnis: Der Test hängt nicht von der echten Welt ab, prüft aber, dass die richtige Methode aufgerufen wurde!
6. Explizite Interface-Implementierung zur Konfliktlösung
Manchmal muss eine Klasse zwei Interfaces mit gleichnamigen Methoden implementieren, aber die Logik dieser Methoden ist unterschiedlich.
public interface IFlyable
{
void Move();
}
public interface ISwimmable
{
void Move();
}
public class Duck : IFlyable, ISwimmable
{
// Explizite Implementierung
void IFlyable.Move()
{
Console.WriteLine("Die Ente fliegt!");
}
void ISwimmable.Move()
{
Console.WriteLine("Die Ente schwimmt!");
}
}
Duck duck = new Duck();
// duck.Move(); // Fehler: Methode existiert nicht!
((IFlyable)duck).Move(); // "Die Ente fliegt!"
((ISwimmable)duck).Move(); // "Die Ente schwimmt!"
Sieht vielleicht hart aus, aber manchmal verlangen die Spezifikationen genau das!
7. Interfaces im Event-Modell: INotifyPropertyChanged
In .NET gibt es Standard-Interfaces für Events – zum Beispiel, wenn sich in Datenmodellen etwas ändert und das UI das mitbekommen soll (sehr oft in WPF, MAUI und anderen GUIs).
using System.ComponentModel;
public class Person : INotifyPropertyChanged
{
private string name;
public string Name
{
get => name;
set
{
if (name != value)
{
name = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));
}
}
}
public event PropertyChangedEventHandler? PropertyChanged;
}
Der Sinn: Jeder Framework, der Data-Binding unterstützt, erwartet, dass deine Klasse dieses Interface implementiert – dann wird das UI automatisch aktualisiert, wenn sich Properties ändern.
8. Interfaces im “Factory”-Pattern
Interfaces sind super für Factories – Klassen, die verschiedene, aber austauschbare Objekte erzeugen.
public interface ITransport
{
void Move();
}
public class Bicycle : ITransport
{
public void Move() => Console.WriteLine("Wir fahren Fahrrad!");
}
public class Car : ITransport
{
public void Move() => Console.WriteLine("Wir fahren Auto!");
}
public class TransportFactory
{
public static ITransport Create(string type)
{
return type switch
{
"bike" => new Bicycle(),
"car" => new Car(),
_ => throw new ArgumentException("Unbekannter Transporttyp")
};
}
}
ITransport transport = TransportFactory.Create("bike");
transport.Move(); // "Wir fahren Fahrrad!"
9. Interfaces für Events: Beispiel für ein eigenes Event
Wir beschreiben ein Interface für einen “Event-Listener”:
public interface ILoginListener
{
void OnLogin(string userName);
}
// Klasse, die das Event auslöst
public class LoginManager
{
private List<ILoginListener> listeners = new();
public void Subscribe(ILoginListener listener) => listeners.Add(listener);
public void Login(string userName)
{
Console.WriteLine($"Benutzer {userName} ist eingeloggt.");
foreach (var listener in listeners)
listener.OnLogin(userName);
}
}
// Klasse, die das Interface implementiert
public class WelcomeMessage : ILoginListener
{
public void OnLogin(string userName)
{
Console.WriteLine($"Willkommen, {userName}!");
}
}
LoginManager manager = new();
manager.Subscribe(new WelcomeMessage());
manager.Login("User");
// Benutzer User ist eingeloggt.
// Willkommen, User!
Im Vorstellungsgespräch: Wenn jemand fragt – “wie würdest du ein eigenes Event-System bauen?”, erzähl ruhig von Listener-Interfaces!
10. Interfaces für Plugins (Erweiterbarkeit von Anwendungen)
Viele große Anwendungen unterstützen Plugins. Dank Interfaces kann deine App neue Module “on the fly” laden, ohne deren Interna zu kennen.
public interface IPlugin
{
string Name { get; }
void Run();
}
// Deine Anwendung:
public class PluginLoader
{
public void LoadAndRun(IEnumerable<IPlugin> plugins)
{
foreach (var plugin in plugins)
{
Console.WriteLine($"Starte Plugin: {plugin.Name}");
plugin.Run();
}
}
}
Plugins können von Dritten entwickelt werden – Hauptsache, sie implementieren das Interface. Deine App wird erweiterbar!
GO TO FULL VERSION