SOLID-Prinzipien
Einführung
Montagmorgen im Entwicklerteam: Ein kleiner Bug im Warenkorb soll behoben werden. Beim Öffnen der betroffenen Klasse fällt auf, dass sie über 800 Zeilen Code umfasst.
Sie kümmert sich gleichzeitig um Validierung, Datenbankzugriffe, Zahlungslogik und Benachrichtigungen. Ein Kollege ändert ein paar Zeilen für den Bugfix - und plötzlich bricht das gesamte Checkout-System. Alle fragen sich: “Warum ist diese kleine Änderung so gefährlich?”

Solche Situationen entstehen, wenn Code zu stark gekoppelt ist und zu viele Verantwortlichkeiten trägt. Änderungen wirken sich dann wie Kettenreaktionen aus. Genau hier setzen die SOLID-Prinzipien an: Sie geben dir fünf klare Regeln, mit denen du deinen Code modular, verständlich und sicher erweiterbar gestaltest.
In dieser Lerneinheit erfährst du, was hinter SOLID steckt - und wie du sie praktisch anwendest, um solche Probleme zu vermeiden.
Lernziele
Nach dieser Einheit kannst du:
-
SOLID-Prinzipien benennen und erklären Du kannst die fünf Prinzipien (SRP, OCP, LSP, ISP, DIP) in eigenen Worten definieren und deren Nutzen für wartbaren, klaren Code darstellen.
-
Negativ- und Positivbeispiele erkennen und bewerten Du erkennst typische Verstöße gegen SOLID (z. B. God Classes, wachsende if-Ketten) und kannst sie mit verbesserten Lösungen vergleichen.
-
Prinzipien praktisch anwenden Du bist in der Lage, einfache Klassen und Methoden so zu strukturieren, dass sie die SOLID-Regeln einhalten (z. B. klare Verantwortlichkeiten, Abhängigkeiten über Abstraktionen).
-
Vorteile im Team- und Projektkontext erläutern Du kannst erklären, wie SOLID-Prinzipien Code-Reviews, Onboarding und langfristige Wartung erleichtern und warum sie für professionelle Softwareentwicklung relevant sind.
Überleitung
Die SOLID-Prinzipien sind fünf Regeln, die dir helfen, objektorientierten Code verständlich, erweiterbar und wartbar zu schreiben. Wenn du sie anwendest, bleiben Änderungen lokal, Tests werden einfacher und Abhängigkeiten überschaubar.
Schauen wir uns diese Prinzipien genauer an.
Was bedeutet SOLID?
- Single Responsibility Principle (SRP):
- Eine Klasse hat genau eine Verantwortlichkeit.
- Open/Closed Principle (OCP):
- Für Erweiterung offen, für Änderung geschlossen.
- Liskov Substitution Principle (LSP):
- Ein Untertyp muss den Basistyp ohne Überraschungen ersetzen können.
- Interface Segregation Principle (ISP):
- Kleine, passende Schnittstellen statt “Alles-in-einem”.
- Dependency Inversion Principle (DIP):
- High-Level-Code hängt von Abstraktionen, nicht von konkreten Klassen ab.
Single Responsibility Principle (SRP)
Eine Klasse oder Funktion sollte genau einen Grund für eine Änderung haben.

Negativbeispiel (Python)
class UserService:
def register(self, data):
# 1) Eingabe prüfen
if "email" not in data or "@" not in data["email"]:
raise ValueError("Ungültige E-Mail")
# 2) Speichern
print("Speichere Nutzer:", data)
# 3) Bestätigungs-Mail senden
print("Sende Mail an:", data["email"])Erklärung:
Dieser Python-Code definiert eine Klasse UserService mit einer Funktion (Methode) register, die einen neuen Benutzer registriert.
- Validierung: Zuerst prüft die Funktion, ob die Eingabedaten (
data) überhaupt einen Eintrag"email"enthalten und ob dieser Wert ein"@"-Zeichen besitzt. - Fehlerfall: Wenn eines von beiden nicht zutrifft, bricht die Funktion sofort mit einem
ValueError(Fehler wegen ungültiger Daten) ab. - Erfolgsfall: Nur wenn die E-Mail gültig ist, werden die nächsten Schritte (hier als
print-Befehle simuliert) ausgeführt: das “Speichern” des Nutzers und das “Senden” einer Bestätigungsmail.
Warum problematisch?
Eine Klasse macht Validierung, Speichern und Mailversand. Jede Änderung (z. B. Mail-Layout) betrifft dieselbe Klasse. Das fördert Seiteneffekte und erschwert Tests.
S - Single Responsibility Principle (SRP)
Positivbeispiel (Python)
class UserValidator:
def is_valid(self, data):
return "email" in data and "@" in data["email"]
class UserRepository:
def save(self, data):
print("Speichere Nutzer:", data)
class Mailer:
def send_welcome(self, email):
print("Sende Mail an:", email)
def register_user(data, validator, repo, mailer):
if not validator.is_valid(data):
raise ValueError("Ungültige E-Mail")
repo.save(data)
mailer.send_welcome(data["email"])Erklärung:
Dieser Code verfolgt das Prinzip der Trennung der Verantwortlichkeiten (Separation of Concerns). Jede Hauptaufgabe (Validieren, Speichern, Mailen) wird in eine eigene, spezialisierte Klasse ausgelagert, anstatt alles in einer Funktion zu vermischen.
-
Die “Spezialisten” (Klassen):
UserValidator: Ist nur für die Gültigkeitsprüfung der Daten zuständig. Dieis_valid-Funktion gibtTrue(wahr) oderFalse(falsch) zurück.UserRepository: Ist nur für das Speichern der Daten (z.B. in einer Datenbank) zuständig. Hier wird das Speichern durchprintsimuliert.Mailer: Ist nur für das Versenden von E-Mails zuständig (ebenfalls simuliert).
-
Der “Koordinator” (Funktion):
register_user: Diese Funktion ist der “Manager” des Prozesses. Sie führt keine Arbeit selbst aus, sondern delegiert sie an die Spezialisten (validator,repo,mailer), die ihr übergeben werden.- Ablauf:
- Sie fragt den
validator, ob die Daten gültig sind. - Wenn nicht (
if not ...), bricht sie mit einem Fehler ab. - Wenn die Daten gültig sind, befiehlt sie dem
repodas Speichern. - Anschließend befiehlt sie dem
mailerdas Senden der E-Mail.
- Sie fragt den
Warum besser?
Jede Klasse hat eine Aufgabe. Du testest und änderst Teile unabhängig.
O – Open/Closed Principle (OCP)
Bestehender Code bleibt unangetastet, wenn du neue Fälle ergänzt.

Negativbeispiel (wachsende if/elif-Kette)
def shipping_cost(kind, weight):
if kind == "standard":
return 5 + 0.5 * weight
elif kind == "express":
return 10 + 0.7 * weight
elif kind == "overnight":
return 20 + 1.0 * weight
# Beim nächsten Tarif wächst die Kette weiter ...Erklärung:
Dieser Code definiert eine Funktion shipping_cost, die Versandkosten basierend auf der Versandart (kind) und dem Gewicht (weight) berechnet.
- Funktionsdefinition:
def shipping_cost(kind, weight):startet die Funktion, die zwei Argumente erwartet:kind(z.B. “standard”) undweight(z.B. 10.5). - Bedingte Preisberechnung: Die Funktion nutzt eine
if/elif-Kette (else if), um zu entscheiden, welche Berechnungsformel angewendet wird:if kind == "standard": Wenn die Art “standard” ist, beträgt der Preis 5 (Grundgebühr) plus 0.5 pro Gewichtseinheit.elif kind == "express": Wenn es nicht “standard”, aber “express” ist, beträgt der Preis 10 (Grundgebühr) plus 0.7 pro Gewichtseinheit.elif kind == "overnight": Wenn es weder “standard” noch “express”, aber “overnight” ist, beträgt der Preis 20 (Grundgebühr) plus 1.0 pro Gewichtseinheit.
- Rückgabe: Das
return-Schlüsselwort beendet die Funktion sofort und gibt das berechnete Ergebnis zurück.
Warum problematisch?
Jede neue Versandart zwingt dich, bestehenden Code zu ändern. Das erhöht Risiko und Merge-Konflikte.
O – Open/Closed Principle (OCP)
Positivbeispiel (Strategie über Objekte)
class ShippingMethod:
def cost(self, weight):
raise NotImplementedError
class Standard(ShippingMethod):
def cost(self, weight):
return 5 + 0.5 * weight
class Express(ShippingMethod):
def cost(self, weight):
return 10 + 0.7 * weight
def shipping_cost(method: ShippingMethod, weight):
return method.cost(weight)
# Nutzung:
price = shipping_cost(Express(), 3)Erklärung:
Dieser Code löst dasselbe Problem wie das vorherige if/elif-Beispiel, aber durch Vererbung (Object-Oriented Programming). Dies ist ein sehr gängiges Muster (genannt “Strategy Pattern”), um Code flexibler zu machen.
-
Die “Vorlage” (Mutterklasse):
class ShippingMethod:: Dies ist eine abstrakte Vorlage (manchmal “Interface” genannt).raise NotImplementedError: Sie legt einen “Vertrag” fest: Jede Versandart, die vonShippingMethoderbt, muss eine eigenecost()-Funktion implementieren, sonst stürzt das Programm ab.
-
Die “konkreten” Versandarten (Kindklassen):
class Standard(ShippingMethod):undclass Express(ShippingMethod):: Dies sind spezifische Versandarten, die von derShippingMethod-Vorlage erben.- Beide “erfüllen den Vertrag”, indem sie die
cost()-Funktion mit ihrer eigenen, spezifischen Logik überschreiben.
-
Die Berechnungs-Funktion:
def shipping_cost(method: ShippingMethod, weight):Diese Funktion ist nun viel einfacher. Sie benötigt keineif/elif-Kette mehr.- Sie akzeptiert irgendein Objekt (
method), solange es demShippingMethod-”Vertrag” entspricht (also eine.cost()-Funktion hat). return method.cost(weight): Sie ruft einfach die.cost()-Funktion des Objekts auf, das ihr übergeben wurde.
-
Nutzung:
shipping_cost(Express(), 3): Hier wird ein neuesExpress-Objekt (Express()) erstellt und an die Funktion übergeben. Die Funktion ruftExpress().cost(3)auf, ohne wissen zu müssen, dass es “Express” ist.
Warum besser?
Neue Versandart = neue Klasse. shipping_cost bleibt unverändert.
L – Liskov Substitution Principle (LSP)
Ein Untertyp darf keine Regeln des Basistyps brechen. Er muss austauschbar sein.

Negativbeispiel (Untertyp kann geforderte Fähigkeit nicht erfüllen)
class Bird:
def fly(self):
print("Ich fliege")
class Penguin(Bird):
def fly(self):
raise NotImplementedError("Pinguine fliegen nicht")Erklärung:
Dieser Code zeigt ein klassisches Problem bei der Vererbung, bekannt als das Liskov-Substitutionsprinzip (LSP).
-
Die “Mutterklasse” (Basisklasse):
class Bird:: Definiert die allgemeine Vorlage für einen Vogel.def fly(self):: Geht davon aus, dass die Standardaktion eines Vogels das Fliegen ist.
-
Die “Kindklasse” (Abgeleitete Klasse):
class Penguin(Bird):: EinPenguin(Pinguin) erbt vonBird. Das bedeutet, er “ist ein” Vogel und bekommt zunächst alle seine Fähigkeiten, einschließlichfly().
-
Das Problem und die “Lösung” hier:
- Pinguine sind Vögel, können aber nicht fliegen. Die geerbte
fly()-Funktion ist für Pinguine falsch. def fly(self):(im Pinguin): Der Pinguin überschreibt die geerbte Funktion.raise NotImplementedError(...): Anstatt zu fliegen, löst die Funktion des Pinguins absichtlich einen Fehler aus. Dies signalisiert, dass diefly()-Funktion für einPenguin-Objekt nicht verwendet werden darf.
- Pinguine sind Vögel, können aber nicht fliegen. Die geerbte
Im Grunde korrigiert die Penguin-Klasse die falsche Annahme der Bird-Klasse (dass alle Vögel fliegen).
Warum problematisch?
Penguin verletzt die Erwartung: Jeder Bird soll fliegen können. Code, der Bird nutzt, stürzt bei Penguin ab.
L – Liskov Substitution Principle (LSP)
Positivbeispiel (passende Typen trennen)
class Bird:
pass
class FlyableBird(Bird):
def fly(self):
print("Ich fliege")
class Penguin(Bird):
def swim(self):
print("Ich schwimme")Erklärung:
Dieser Code löst das Problem aus dem vorherigen Beispiel (“Pinguine können nicht fliegen”) durch eine bessere Vererbungsstruktur.
class Bird:: Dies ist jetzt eine sehr allgemeine “Basis”-Klasse. Sie dient nur als gemeinsamer Vorfahre (Marker).passist ein Platzhalter und bedeutet “tut nichts”.class FlyableBird(Bird):: Dies ist eine spezialisierte Unterklasse (Kindklasse) für flugfähige Vögel. Sie erbt vonBird(ist also ein Vogel) und fügt die Fähigkeitfly()hinzu.class Penguin(Bird):: DerPenguinerbt ebenfalls vonBird(ist ein Vogel), aber er erbt nicht vonFlyableBird.- Ergebnis: Der Pinguin hat korrekterweise keine
fly()-Funktion. Stattdessen hat er seine eigeneswim()-Funktion.
- Ergebnis: Der Pinguin hat korrekterweise keine
Dieser Aufbau ist besser, da eine Klasse (Pinguin) nicht mehr gezwungen wird, eine geerbte Funktion (fliegen) absichtlich “kaputt” zu machen.
Warum besser?
Nur Vögel, die fliegen, erben von FlyableBird. Penguin bricht keine Erwartung mehr.
I - Interface Segregation (ISP)
Interface Segregation Principle (ISP)
Kein Code soll gezwungen sein, von Methoden abzuhängen, die er nicht nutzt. Zerlege große Interfaces in kleine, zweckmäßige Interfaces.

Nutzen: Weniger Kopplung, bessere Kohäsion, klarere Verträge. Änderungen bleiben lokal, Tests werden einfacher.
Negativbeispiel (zu großes Interface – unnötige Methoden)
class MultiFunctionDevice:
def print(self, text: str) -> None:
...
def scan(self) -> bytes:
...
def fax(self, number: str) -> None:
...
class SimplePrinter(MultiFunctionDevice):
def print(self, text: str) -> None:
print(text)
def scan(self) -> bytes:
raise NotImplementedError # braucht SimplePrinter nicht
def fax(self, number: str) -> None:
raise NotImplementedError # braucht SimplePrinter nichtErklärung:
Dieser Code demonstriert ein Problem, das durch zu “breite” oder “fette” Vorlagen (Klassen/Interfaces) entsteht, bekannt als Verletzung des Interface Segregation Principle (ISP).
-
Die “Alles-Könner”-Vorlage (Mutterklasse):
class MultiFunctionDevice: Definiert eine Vorlage für ein Multifunktionsgerät. Es setzt voraus, dass jedes Gerät dieser Art drucken, scannen und faxen kann. (Das...ist ein Platzhalter).
-
Die “Spezialisten”-Klasse (Kindklasse):
class SimplePrinter(MultiFunctionDevice): Ein einfacher Drucker erbt von derMultiFunctionDevice-Vorlage.- Dadurch “verspricht” der
SimplePrinter, alles zu können, was einMultiFunctionDevicekann (drucken, scannen, faxen).
-
Das Problem:
- Der
SimplePrinterkann dieprint-Funktion erfüllen. - Er kann jedoch nicht scannen oder faxen. Er wird durch die Vererbung gezwungen, diese Funktionen zu implementieren, obwohl er sie nicht unterstützt.
- Wie im Pinguin-Beispiel “löst” er dies, indem er bei Aufruf von
scan()oderfax()einen Fehler (NotImplementedError) auslöst.
- Der
Das Kernproblem ist, dass SimplePrinter gezwungen wird, von Funktionen “abhängig” zu sein (scan, fax), die er gar nicht benötigt.
Warum ist das schlecht?
Der Klient hängt von unnötigen Methoden ab. Jede Änderung an scan/fax betrifft auch Geräte, die nur drucken.
I - Interface Segregation (ISP)
Positivbeispiel (kleine, fokusierte Interfaces)
from abc import ABC, abstractmethod
class Printer(ABC):
@abstractmethod
def print(self, text: str) -> None: ...
class Scanner(ABC):
@abstractmethod
def scan(self) -> bytes: ...
class SimplePrinter(Printer):
def print(self, text: str) -> None:
print(text)
class PrintScanDevice(Printer, Scanner):
def print(self, text: str) -> None:
print(text)
def scan(self) -> bytes:
return b"image"Erklärung:
Dieser Code löst das Problem aus dem vorigen Beispiel (“fette” Vorlagen), indem er das Interface Segregation Principle (ISP) anwendet. Die Idee ist, “Alles-Könner”-Vorlagen in kleinere, spezifische “Rollen”-Vorlagen aufzuteilen.
-
Import (abc):
ABC(Abstract Base Class) undabstractmethodsind Werkzeuge, um formale “Vorlagen” oder “Verträge” in Python zu erstellen. -
Getrennte Vorlagen (Interfaces):
class Printer(ABC):Definiert einen “Vertrag” nur für das Drucken.@abstractmethodbedeutet: Jede Klasse, die vonPrintererbt, muss eineprint-Funktion haben.class Scanner(ABC):Definiert einen separaten “Vertrag” nur für das Scannen.
-
Spezialisierte Klasse:
class SimplePrinter(Printer):Diese Klasse erbt nur vonPrinter. Sie muss nur denPrinter-Vertrag erfüllen (dieprint-Funktion bereitstellen). Sie wird nicht mehr gezwungen,scan()oderfax()zu implementieren.
-
Kombinierte Klasse (Mehrfachvererbung):
class PrintScanDevice(Printer, Scanner):Dieses Gerät kann beides. Es erbt vonPrinterundScanner.- Daher muss es beide Verträge erfüllen und sowohl eine
print()- als auch einescan()-Funktion implementieren.
Was ist besser?
SimplePrinter implementiert nur print(). Ein Kombigerät kann optional scan() ergänzen. Klienten sehen nur die Methoden, die sie wirklich brauchen.
D - Dependency Inversion Principle (DIP)
Hochrangige Module sollen nicht von konkreten, niederrangigen Modulen abhängen. Beide hängen von Abstraktionen ab.

Praxis: Nutze Dependency Injection (Übergabe im Konstruktor oder als Parameter), um Implementierungen zur Laufzeit auszutauschen.
Nutzen: Geringere Kopplung, besser testbar (Mocks/Stubs), einfacherer Technologiewechsel.
Negativbeispiel (harte Kopplung durch direkte Instanziierung)
class SmtpMailer:
def send(self, to: str, body: str) -> None:
print(f"SMTP -> {to}: {body}")
class NotificationService:
def __init__(self):
self.mailer = SmtpMailer() # feste Bindung
def notify(self, to: str, msg: str) -> None:
self.mailer.send(to, msg)Erklärung
Dieser Code demonstriert das Problem der starken Kopplung (Strong Coupling), oft auch als feste Bindung bezeichnet.
SmtpMailer: Dies ist eine “konkrete” Implementierung, die E-Mails über SMTP versendet (hier simuliert durchprint).NotificationService: Dies ist ein Dienst, der Benachrichtigungen versenden soll.- Das Kernproblem (
__init__): Im Konstruktor (der__init__-Funktion) erstellt derNotificationServiceseine Abhängigkeit (denSmtpMailer) selbst. - Die Konsequenz: Der
NotificationServiceist untrennbar an die spezifische KlasseSmtpMailergebunden. Man kann den Mailer nicht einfach austauschen (z.B. gegen einenApiMaileroder einenTestMailerfür Tests), ohne den Code desNotificationServiceändern zu müssen.
Warum ist das schlecht? Tests werden schwerer (kein einfacher Austausch), und Technologie-Änderungen greifen in High-Level-Logik ein.
D - Dependency Inversion Principle (DIP)
Positivbeispiel (Abhängigkeit injizieren, auf Abstraktion zielen)
from abc import ABC, abstractmethod
class Mailer(ABC):
@abstractmethod
def send(self, to: str, body: str) -> None: ...
class SmtpMailer(Mailer):
def send(self, to: str, body: str) -> None:
print(f"SMTP -> {to}: {body}")
class ApiMailer(Mailer):
def send(self, to: str, body: str) -> None:
print(f"API -> {to}: {body}")
class NotificationService:
def __init__(self, mailer: Mailer) -> None:
self.mailer = mailer # Abhängigkeit wird übergeben
def notify(self, to: str, msg: str) -> None:
self.mailer.send(to, msg)
# Nutzung
service = NotificationService(SmtpMailer()) # oder NotificationService(ApiMailer())Erklärung
Dieser Code löst das Problem der “festen Bindung” aus dem vorigen Beispiel durch Dependency Inversion (Umkehrung der Abhängigkeit), umgesetzt durch Dependency Injection (Einreichen der Abhängigkeit).
-
Die Abstraktion (Der “Vertrag”):
class Mailer(ABC):Dies ist eine abstrakte Vorlage (ein “Interface”). Sie definiert, dass jederMailereinesend()-Funktion haben muss, sagt aber nicht, wie diese funktioniert.
-
Die Konkreten Implementierungen (Die “Arbeiter”):
SmtpMailer(Mailer)undApiMailer(Mailer): Dies sind spezifische Mailer-Typen, die beide denMailer-Vertrag erfüllen (sie implementierensend()).
-
Die Umkehrung (Der Kernpunkt):
class NotificationService:: Diese Klasse erstellt den Mailer nicht mehr selbst.def __init__(self, mailer: Mailer) -> None:: Stattdessen verlangt der Konstruktor (__init__), dass ihm ein fertigesMailer-Objekt von außen übergeben wird. Dies nennt man Dependency Injection (Einreichen der Abhängigkeit).self.mailer = mailer: Der Service speichert nur die Referenz auf den übergebenen Mailer.
-
Nutzung (Die Flexibilität):
service = NotificationService(SmtpMailer()): Beim Erstellen desNotificationServicewird von außen entschieden, welcher Mailer verwendet wird (hierSmtpMailer).- Der
NotificationServiceist nun “entkoppelt”. Er funktioniert mit jedem Objekt, das denMailer-Vertrag erfüllt, egal ob es einSmtpMailer,ApiMaileroder einTestMailerist.
Was ist besser?
NotificationService hängt von der Abstraktion Mailer ab. Die konkrete Implementierung kommt von außen. Du tauschst sie, ohne den Service zu ändern.
Vorteile und Probleme
Die folgende Tabelle fasst die wichtigsten Vorteile und typische Probleme bei Missachtung:
| Vorteile im Alltag | Häufige Probleme ohne SOLID |
|---|---|
| Lesbarkeit & Wartbarkeit: Verantwortlichkeiten sind klar, Abhängigkeiten laufen über Abstraktionen | God Classes: zu viele Gründe zur Änderung (SRP-Verstoß) |
| Weniger Seiteneffekte: Änderungen bleiben lokal | Hohe Kopplung: direkte Abhängigkeiten auf konkrete Klassen (DIP-Verstoß) |
| Bessere Tests: einfache Mocks/Stubs dank kleiner Interfaces und Injektion | Aufgeblähte Interfaces: Klienten müssen unnötige Methoden mitziehen (ISP-Verstoß) |
Anwendungstipps und Kontext
Anwendungstipps
- Klein anfangen: Beim nächsten Feature große Interfaces aufteilen (ISP) und Abhängigkeiten injizieren (DIP).
- Schrittweise refactoren: Erst eine Stelle entkoppeln, Tests ergänzen, dann weitermachen.
- Team-Vokabular nutzen: „Bitte Interface aufteilen (ISP)“ / „Abstraktion + Injection (DIP)“ macht Reviews schneller.
Kontext / Herkunft
Die SOLID-Prinzipien wurden von Robert C. Martin konsolidiert; das Akronym „SOLID“ prägte Michael Feathers. Das Liskov Substitution Principle geht auf Barbara Liskov (1987) zurück.
Zusammenfassung
Zusammenfassung:
Single Responsibility Principle (SRP)
Dieses Prinzip fordert, dass eine Klasse nur eine Verantwortung haben darf.
- Negativbeispiel: Eine Klasse validiert Nutzerdaten und speichert sie zugleich → viele Gründe zur Änderung.
- Positivbeispiel: Eine UserValidator-Klasse prüft Daten, eine UserRepository-Klasse speichert sie.
- Nutzen: Klarere Verantwortlichkeiten, weniger Seiteneffekte, bessere Testbarkeit.
Open/Closed Principle (OCP)
Eine Softwareeinheit soll für Erweiterungen offen, aber für Änderungen geschlossen sein.
- Negativbeispiel: Neue Zahlungsarten erfordern Anpassungen in bestehenden Methoden.
- Positivbeispiel: Ein PaymentMethod-Interface erlaubt neue Zahlungsarten, ohne die Checkout-Logik zu ändern.
- Nutzen: Stabilität des Codes bei Erweiterungen, weniger Fehler durch Änderungen.
Liskov Substitution Principle (LSP)
Subtypen müssen sich so verhalten, dass sie Objekte ihres Basistyps problemlos ersetzen können.
- Negativbeispiel: Ein Square bricht Erwartungen an Rectangle, weil Setter-Methoden unerwartet wirken.
- Positivbeispiel: Korrekte Modellierung mit getrennten Klassen vermeidet Verletzungen von Erwartungen.
- Nutzen: Austauschbarkeit von Klassen ohne unerwartete Effekte.
Interface Segregation Principle (ISP)
Klassen sollen nur von Methoden abhängen, die sie wirklich brauchen.
- Negativbeispiel:
SimplePrintermuss unnötige Methoden wiescanoderfaximplementieren. - Positivbeispiel: Kleine, spezifische Interfaces wie Printer und Scanner erlauben passgenaue Implementierungen.
- Nutzen: Weniger Kopplung, schlankere Verträge, einfachere Tests.
Dependency Inversion Principle (DIP)
Hochrangige Module sollen nicht von konkreten Klassen, sondern von Abstraktionen abhängen.
- Negativbeispiel:
NotificationServiceerzeugt selbst einSmtpMailer-Objekt → feste Bindung. - Positivbeispiel: Ein
Mailer-Interface erlaubt die Übergabe beliebiger Implementierungen (z. B. SMTP oder API-Mailer). - Nutzen: Austauschbarkeit, einfache Tests mit Stubs/Mocks, geringere Abhängigkeiten.
Gesamtvorteile von SOLID
- Lesbarkeit und Wartbarkeit steigen.
- Änderungen verursachen weniger Seiteneffekte.
- Erweiterungen sind planbarer.
- Teams nutzen ein gemeinsames Vokabular (SRP, OCP, LSP, ISP, DIP), was Code-Reviews und Onboarding erleichtert.
Häufige Probleme ohne SOLID
- „God Classes“ mit zu vielen Verantwortlichkeiten.
- Harte Kopplung an konkrete Implementierungen.
- Interfaces mit unnötigen Methoden.
- Zunehmende Softwarefäulnis durch unstrukturierte Änderungen.
Ausblick:
In der nächsten Lerneinheit geht es um das Model-View-Controller (MVC)-Prinzip – ein grundlegendes Architekturkonzept, das für saubere Trennung von Logik, Darstellung und Daten sorgt. Du wirst sehen, wie MVC hilft, Anwendungen übersichtlicher zu strukturieren, Änderungen einfacher umzusetzen und Teams effizienter zusammenarbeiten zu lassen.