Grundlagen der Dependency Injection
Teil des Abschnitts Objektorientierte Programmierung der C#-Journey von Coddy. Lektion 46 von 70.
Dependency Injection (DI) ist eine Technik, bei der eine Klasse ihre Abhängigkeiten von außen erhält, anstatt sie selbst zu erstellen. Dadurch wird der Code flexibler, besser testbar und lose gekoppelt.
Ohne DI erstellt eine Klasse ihre eigenen Abhängigkeiten, wodurch Änderungen und Tests erschwert werden:
public class OrderService
{
private EmailSender sender = new EmailSender(); // Eng gekoppelt
public void PlaceOrder() => sender.Send("Order placed");
}Bei DI werden Abhängigkeiten über den Konstruktor übergeben:
public interface IMessageSender
{
void Send(string message);
}
public class EmailSender : IMessageSender
{
public void Send(string message) => Console.WriteLine("Email: " + message);
}
public class OrderService
{
private IMessageSender sender;
public OrderService(IMessageSender sender)
{
this.sender = sender;
}
public void PlaceOrder() => sender.Send("Order placed");
}Jetzt weiß OrderService nicht und kümmert sich auch nicht darum, ob es E-Mail, SMS oder einen Mock-Sender zum Testen verwendet. Die Abhängigkeit wird beim Erstellen des Objekts „injiziert“:
IMessageSender emailSender = new EmailSender();
OrderService service = new OrderService(emailSender);
service.PlaceOrder(); // E-Mail: Bestellung aufgegebenDieses Muster folgt dem Prinzip, von Abstraktionen (Schnittstellen) statt von konkreten Implementierungen abzuhängen. Es ermöglicht das Austauschen von Implementierungen, ohne die Klasse zu ändern, die sie verwendet, was für Unit-Tests und die Aufrechterhaltung flexibler Architekturen unerlässlich ist.
Aufgabe
EinfachWir erstellen ein Protokollierungssystem, das die Leistungsfähigkeit von Dependency Injection demonstriert. Statt fest zu codieren, wie Protokolle geschrieben werden, erstellst du eine flexible Logger-Klasse, die mit jedem Protokollierungsziel – Konsole, Dateisimulation oder etwas anderem – arbeiten kann, ohne die Einzelheiten zu kennen.
Du organisierst deinen Code über drei Dateien:
ILogWriter.cs: Definiere im NamespaceILogWriterein Interface namensLogging. Dieses Interface stellt den Vertrag für jedes Protokollierungsziel dar und sollte eine einzige MethodeWrite(string message)besitzen, die einen String zurückgibt, der beschreibt, wohin die Nachricht geschrieben wurde.LogWriters.cs: Erstelle im NamespaceILogWriterzwei konkrete Implementierungen vonLogging:ConsoleLogWriter– die MethodeWritegibtConsole: {message}zurückFileLogWriter– erhält seinen Konstruktor über einen Dateinamen, und seine MethodeWritegibtFile[{filename}]: {message}zurück
Logger.cs: Erstelle im NamespaceLoggereineLogging-Klasse, die ihre Abhängigkeit über Konstruktorinjektion erhält. DerLoggersollte:- Über seinen Konstruktor ein
ILogWriterakzeptieren und es in einem privaten Feld speichern - Eine Methode
Log(string message)besitzen, die die Aufgabe an den injizierten Writer delegiert und das Ergebnis zurückgibt
- Über seinen Konstruktor ein
Program.cs: Demonstriere Dependency Injection, indem du zwei verschiedene Logger erstellst – einen mit einemConsoleLogWriterund einen mit einemFileLogWriter. Zeige, wie sich dieselbeLogger-Klasse abhängig davon, welche Abhängigkeit injiziert wird, unterschiedlich verhält.
Du erhältst zwei Eingaben:
- Den Dateinamen für den Datei-Logger
- Die zu protokollierende Nachricht
Erstelle einen Logger mit einem ConsoleLogWriter und protokolliere die Nachricht. Erstelle anschließend einen weiteren Logger mit einem FileLogWriter (unter Verwendung des Dateinamens aus der Eingabe) und protokolliere dieselbe Nachricht. Gib jedes Ergebnis in einer eigenen Zeile aus.
Wenn die Eingaben beispielsweise app.log und System started lauten, sollte die Ausgabe folgendermaßen aussehen:
Console: System started
File[app.log]: System startedBeachte, dass sich die Logger-Klasse niemals ändert – sie funktioniert mit jeder ILogWriter-Implementierung. Das ist das Wesentliche von Dependency Injection: Der Logger hängt von einer Abstraktion (dem Interface) ab, und die konkrete Implementierung wird von außen „injiziert“. Später könntest du problemlos einen DatabaseLogWriter oder CloudLogWriter hinzufügen, ohne die Logger-Klasse überhaupt zu ändern!
Probier es selbst
using System;
using Logging;
class Program
{
public static void Main(string[] args)
{
// Eingaben lesen
string filename = Console.ReadLine();
string message = Console.ReadLine();
// TODO: Erstelle einen Logger mit einem ConsoleLogWriter und protokolliere die Nachricht
// Gib das Ergebnis aus
// TODO: Erstelle einen weiteren Logger mit einem FileLogWriter (unter Verwendung von filename) und protokolliere dieselbe Nachricht
// Gib das Ergebnis 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 – Formenrechner8Fortgeschrittene OOP-Konzepte
Komposition statt VererbungGenerics (Klassen & Methoden)Delegates und EventsAttribute und ReflectionIDisposable & using-AnweisungGrundlagen der Dependency Injection3Klassenarchitektur
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