Dependency Injection Grundlagen
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. Dies macht den Code flexibler, testbarer und lose gekoppelt.
Ohne DI erstellt eine Klasse ihre eigenen Abhängigkeiten, was Änderungen oder Tests erschwert:
public class OrderService
{
private EmailSender sender = new EmailSender(); // Eng gekoppelt
public void PlaceOrder() => sender.Send("Order placed");
}Mit 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 mehr und es ist ihm egal, ob E-Mail, SMS oder ein Mock-Sender für Tests verwendet wird. Die Abhängigkeit wird beim Erstellen des Objekts „injiziert“:
IMessageSender emailSender = new EmailSender();
OrderService service = new OrderService(emailSender);
service.PlaceOrder(); // Email: Bestellung aufgegebenDieses Muster folgt dem Prinzip, von Abstraktionen (Schnittstellen) statt von konkreten Implementierungen abhängig zu sein. Es ermöglicht den Austausch von Implementierungen, ohne die Klasse zu ändern, die sie verwendet, was für Unit-Tests und die Aufrechterhaltung flexibler Architekturen unerlässlich ist.
Aufgabe
EinfachLassen Sie uns ein Logging-System bauen, das die Leistungsfähigkeit von Dependency Injection demonstriert. Anstatt fest zu verdrahten, wie Protokolle geschrieben werden, erstellen Sie eine flexible Logger-Klasse, die mit jedem Protokollziel arbeiten kann – Konsole, Dateisimulation oder etwas anderem – ohne die Details zu kennen.
Sie werden Ihren Code über drei Dateien organisieren:
ILogWriter.cs: Definieren Sie ein Interface namensILogWriterim NamespaceLogging. Dieses Interface repräsentiert den Vertrag für jedes Protokollziel und sollte eine einzige MethodeWrite(string message)haben, die einen String zurückgibt, der beschreibt, wohin die Nachricht geschrieben wurde.LogWriters.cs: Erstellen Sie zwei konkrete Implementierungen vonILogWriterim NamespaceLogging:ConsoleLogWriter– seineWrite-Methode gibtConsole: {message}zurückFileLogWriter– nimmt einen Dateinamen über seinen Konstruktor entgegen und seineWrite-Methode gibtFile[{filename}]: {message}zurück
Logger.cs: Erstellen Sie eineLogger-Klasse im NamespaceLogging, die ihre Abhängigkeit über Constructor Injection erhält. DerLoggersollte:- Einen
ILogWriterüber seinen Konstruktor akzeptieren und in einem privaten Feld speichern - Eine
Log(string message)-Methode haben, die an den injizierten Writer delegiert und das Ergebnis zurückgibt
- Einen
Program.cs: Demonstrieren Sie Dependency Injection, indem Sie zwei verschiedene Logger erstellen – einen mit einemConsoleLogWriterund einen mit einemFileLogWriter. Zeigen Sie, wie sich dieselbeLogger-Klasse unterschiedlich verhält, je nachdem, welche Abhängigkeit injiziert wird.
Sie erhalten zwei Eingaben:
- Den Dateinamen für den Datei-Logger
- Die zu protokollierende Nachricht
Erstellen Sie einen Logger mit einem ConsoleLogWriter und protokollieren Sie die Nachricht. Erstellen Sie dann einen weiteren Logger mit einem FileLogWriter (unter Verwendung des Dateinamens aus der Eingabe) und protokollieren Sie dieselbe Nachricht. Geben Sie jedes Ergebnis in einer eigenen Zeile aus.
Wenn die Eingaben beispielsweise app.log und System started sind, sollte die Ausgabe wie folgt aussehen:
Console: System started
File[app.log]: System startedBeachten Sie, dass sich die Logger-Klasse nie ändert – sie funktioniert mit jeder ILogWriter-Implementierung. Dies ist der Kern von Dependency Injection: Der Logger hängt von einer Abstraktion (dem Interface) ab, und die konkrete Implementierung wird von außen "injiziert". Sie könnten später problemlos einen DatabaseLogWriter oder CloudLogWriter hinzufügen, ohne die Logger-Klasse überhaupt zu ändern!
Spickzettel
Dependency Injection (DI) ist eine Technik, bei der eine Klasse ihre Abhängigkeiten von außen erhält, anstatt sie selbst zu erstellen, wodurch der Code flexibler, testbarer und lose gekoppelt wird.
Ohne DI (eng gekoppelt):
public class OrderService
{
private EmailSender sender = new EmailSender(); // Eng gekoppelt
public void PlaceOrder() => sender.Send("Order placed");
}Mit DI (lose gekoppelt):
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");
}Injizieren der Abhängigkeit:
IMessageSender emailSender = new EmailSender();
OrderService service = new OrderService(emailSender);
service.PlaceOrder(); // Email: Order placedDI folgt dem Prinzip, von Abstraktionen (Schnittstellen) statt von konkreten Implementierungen abzuhängen, was den Austausch von Implementierungen ermöglicht, ohne die Klasse, die sie verwendet, 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
// Das Ergebnis ausgeben
// TODO: Erstelle einen weiteren Logger mit einem FileLogWriter (unter Verwendung von filename) und protokolliere dieselbe Nachricht
// Das Ergebnis ausgeben
}
}
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 Calculator8Fortgeschrittene OOP-Konzepte
Komposition statt VererbungGenerics (Klassen & Methoden)Delegates und EventsAttribute und ReflectionIDisposable & using-AnweisungDependency Injection Grundlagen3Klassenarchitektur
Instanz- vs. statische Daten'readonly' & 'const' SchlüsselwörterBacking FieldsZusammenfassung - Bankkonto-Manager6Kapselung
ZugriffsmodifikatorenProperties zur KapselungImplementierung von Data HidingImmutability-MusterRückblick – Studierendendaten