SOLID-Prinzipien mit C#
SOLID-Prinzipien mit C#-Beispielen: flexiblen, wartbaren und testbaren Code erstellen.
In der Softwareentwicklung sind die SOLID-Prinzipien, definiert von Robert C. Martin, fünf grundlegende Richtlinien, die darauf abzielen, flexiblere, besser lesbare und wartbare Systeme in der objektorientierten Programmierung (OOP) zu schaffen. In C# werden diese Prinzipien häufig als Leitregeln beim Entwurf von Klassen angewendet.
1. Single Responsibility Principle (SRP) – Prinzip der einzigen Verantwortung
Eine Klasse sollte nur eine Verantwortung haben. Das bedeutet, sie sollte nur einen Grund haben, sich zu ändern. Wenn eine Klasse mehrere Aufgaben übernimmt (z. B. Daten speichern + Berichte erstellen), wird sie schwerer wartbar und fehleranfälliger.
// Falsch: Speichert Bestellungen und druckt Rechnungen
public class OrderManager
{
public void SaveOrder(Order order) { /* speichern */ }
public void PrintInvoice(Order order) { /* drucken */ }
}
// Richtig: Jede Klasse hat nur eine Verantwortung
public class OrderRepository
{
public void Save(Order order) { /* speichern */ }
}
public class InvoicePrinter
{
public void Print(Order order) { /* drucken */ }
}
2. Open/Closed Principle (OCP) – Offen/Geschlossen-Prinzip
Klassen sollten offen für Erweiterungen, aber geschlossen für Änderungen sein.
Das bedeutet, dass neue Funktionalitäten hinzugefügt werden können, ohne den bestehenden Code zu ändern.
Dies wird in der Regel mit abstract classes oder interfaces erreicht.
// Falsch: Neue Zahlungsmethoden erfordern Änderungen in der Klasse
public class PaymentService
{
public void Pay(string type)
{
if (type == "CreditCard") { /* ... */ }
else if (type == "PayPal") { /* ... */ }
}
}
// Richtig: Neue Methoden erfordern nur neue Klassen
public interface IPayment
{
void Pay();
}
public class CreditCardPayment : IPayment
{
public void Pay() { /* ... */ }
}
public class PayPalPayment : IPayment
{
public void Pay() { /* ... */ }
}
3. Liskov Substitution Principle (LSP) – Liskovsches Substitutionsprinzip
Abgeleitete Klassen müssen anstelle ihrer Basisklassen verwendbar sein. Mit anderen Worten: Eine Unterklasse sollte überall dort korrekt funktionieren, wo ihre Basisklasse erwartet wird. Eine Unterklasse darf den Vertrag ihrer Basisklasse nicht verletzen.
public abstract class Bird
{
public abstract void Fly();
}
public class Sparrow : Bird
{
public override void Fly() { Console.WriteLine("Spatz fliegt"); }
}
// Falsch: Pinguine können nicht fliegen, Fly() wird erzwungen
public class Penguin : Bird
{
public override void Fly() { throw new NotImplementedException(); }
}
Hier verletzt die Penguin-Klasse das LSP.
Denn die abstrakte Klasse Bird erzwingt die Annahme „alle Vögel können fliegen“.
Stattdessen sollten separate Interfaces wie IFlyingBird und INonFlyingBird definiert werden.
4. Interface Segregation Principle (ISP) – Schnittstellenaufteilungsprinzip
Eine Klasse sollte nicht gezwungen sein, große Interfaces mit Methoden zu implementieren, die sie nicht benötigt. Stattdessen sollten kleinere, zweckgerichtete Interfaces bevorzugt werden.
// Falsch: Alle Tiere müssen gehen, fliegen und schwimmen
public interface IAnimal
{
void Walk();
void Fly();
void Swim();
}
// Richtig: In kleinere Interfaces aufgeteilt
public interface IWalkable { void Walk(); }
public interface IFlyable { void Fly(); }
public interface ISwimmable { void Swim(); }
public class Dog : IWalkable, ISwimmable
{
public void Walk() { /* ... */ }
public void Swim() { /* ... */ }
}
5. Dependency Inversion Principle (DIP) – Abhängigkeitsinversionsprinzip
High-Level-Klassen sollten nicht direkt von Low-Level-Klassen abhängig sein. Beide sollten von Abstraktionen (Interfaces oder abstrakten Klassen) abhängen. Dieses Prinzip bildet die Grundlage für Dependency Injection.
// Falsch: Service hängt direkt von ConsoleLogger ab
public class Service
{
private readonly ConsoleLogger _logger = new ConsoleLogger();
}
// Richtig: Abhängigkeit von einer Abstraktion
public interface ILogger
{
void Log(string msg);
}
public class ConsoleLogger : ILogger
{
public void Log(string msg) => Console.WriteLine(msg);
}
public class Service
{
private readonly ILogger _logger;
public Service(ILogger logger) { _logger = logger; }
}
Vorteile
- Verbessert die Lesbarkeit und Wartbarkeit des Codes.
- Sorgt für eine Architektur, die resistenter gegenüber Änderungen ist.
- Erleichtert das Unit Testing.
- Reduziert Code-Duplizierung und erhöht die Wiederverwendbarkeit.
TL;DR
- SRP: Jede Klasse sollte nur eine Verantwortung haben.
- OCP: Offen für Erweiterungen, geschlossen für Änderungen.
- LSP: Unterklassen sollten Basisklassen ersetzen können.
- ISP: Bevorzuge kleine, fokussierte Interfaces statt großer.
- DIP: Abhängigkeit von Abstraktionen, nicht von konkreten Klassen.
Ähnliche Artikel
Ausnahmebehandlung in C# (try, catch, finally)
Erlernen Sie die Ausnahmebehandlung in C# mit try-, catch- und finally-Blöcken zur sicheren Fehlerverwaltung anhand von Beispielen.
Dependency Injection Grundlagen in C#
Lernen Sie die Grundlagen von Dependency Injection in C#, um Abhängigkeiten zu verwalten und lose Kopplung zu erreichen.
Erweiterungsmethoden in C#
Lernen Sie Erweiterungsmethoden in C#, um bestehenden Typen neue Funktionen hinzuzufügen, ohne den Code zu ändern.
Interfaces und Abstrakte Klassen in C#
Lernen Sie Interfaces und abstrakte Klassen in C#, ihre Unterschiede und den Einsatz für sauberes, erweiterbares Design.
Kapselung, Vererbung und Polymorphismus in C#
Lernen Sie Kapselung, Vererbung und Polymorphismus in C# mit Beispielen, um zentrale OOP-Konzepte sicher anzuwenden.
Klassen, Objekte, Eigenschaften und Methoden in C#
Erlernen Sie die Grundlagen von Klassen, Objekten, Eigenschaften und Methoden in C# für objektorientierte Programmierung.