SOLID-Prinzipien
Warum machen die SOLID-Prinzipien Software besser?
Ein Fundament für professionelle Entwicklung
Du kennst bereits die Grundlagen der objektorientierten Programmierung (OOP) und hast gelernt, wie man Klassen und Beziehungen in UML modelliert. Doch funktionierender Code ist nicht automatisch guter Code. Ohne klare Designregeln entstehen oft unwartbare "Spaghetti-Systeme", bei denen eine kleine Änderung an einer Stelle unerwartete Fehler an ganz anderen Stellen verursacht.
Die SOLID-Prinzipien sind fünf goldene Regeln, die dir helfen, Software zu entwerfen, die leicht verständlich, flexibel erweiterbar und einfach zu warten ist. Das Akronym steht für:
- Single Responsibility Principle (SRP)
- Open/Closed Principle (OCP)
- Liskov Substitution Principle (LSP)
- Interface Segregation Principle (ISP)
- Dependency Inversion Principle (DIP)
Wie sorgen wir für klare Verantwortung und Erweiterbarkeit?
Single Responsibility Principle (SRP)
Das SRP besagt: "Eine Klasse sollte nur einen einzigen Grund haben, sich zu ändern."
Das bedeutet nicht, dass eine Klasse nur eine Methode haben darf, sondern dass sie nur für einen einzigen fachlichen oder technischen Verantwortungsbereich zuständig sein soll.
- Negativ-Beispiel: Stell dir eine Klasse
Rechnungvor. Sie berechnet die Gesamtsumme, speichert die Daten in der Datenbank und generiert ein PDF für den E-Mail-Versand. Wenn sich nun das Datenbank-Passwort ändert, musst du die Klasse anfassen. Wenn das Finanzamt das PDF-Format ändert, musst du dieselbe Klasse anfassen. Die Klasse hat zu viele Gründe für Änderungen (hohe Kopplung, geringe Kohäsion). - Lösung: Teile die Verantwortung auf.
RechnungsBerechner: Kümmert sich nur um die Mathematik.RechnungsRepository: Kümmert sich nur um das Speichern in der Datenbank.RechnungsDrucker: Kümmert sich nur um das Erstellen des PDFs.
Open/Closed Principle (OCP)
Das OCP fordert: "Software-Entitäten (Klassen, Module, Funktionen) sollten offen für Erweiterung, aber geschlossen für Modifikation sein."
Du solltest neue Funktionen hinzufügen können, ohne den bestehenden, bereits getesteten Quellcode verändern zu müssen. Das minimiert das Risiko, neue Fehler in alte Funktionen einzubauen.
- Szenario: Du programmierst eine Gehaltsabrechnung. Bisher gibt es nur
Festangestellte. Nun kommenFreelancerhinzu. - Verletzung des OCP: Du gehst in deine Hauptklasse und fügst überall
if (typ == Freelancer) ...hinzu. Du änderst den bestehenden Code (Modifikation). - Einhaltung des OCP: Du nutzt Polymorphie. Du hast ein Interface
Mitarbeitendermit der MethodeberechneGehalt(). Deine Hauptklasse nutzt nur dieses Interface. UmFreelancerzu unterstützen, erstellst du einfach eine neue KlasseFreelancer, die das Interface implementiert. Der Code der Abrechnungsmaschine muss nicht angefasst werden. Der Code ist "geschlossen" für Änderungen, aber durch die neue Klasse "offen" für Erweiterungen.
class Mitarbeitender:
def berechneGehalt(self) -> float:
pass
class Festangestellter(Mitarbeitender):
def berechneGehalt(self) -> float:
return 3000.0
class Freelancer(Mitarbeitender):
def berechneGehalt(self) -> float:
return 2000.0
class AbrechnungsMaschine:
def generiereAbrechnung(self, mitarbeiter: Mitarbeitender):
gehalt = mitarbeiter.berechneGehalt()
print(f"Gehalt: {gehalt}")Wie gestalten wir flexible Schnittstellen und Beziehungen?
Liskov Substitution Principle (LSP)
Das LSP besagt: "Objekte einer Basisklasse müssen durch Objekte ihrer abgeleiteten Klassen ersetzt werden können, ohne dass das Programm fehlerhaft wird."
Eine Unterklasse darf sich nicht so verhalten, dass sie die Erwartungen, die man an die Elternklasse hat, enttäuscht. Es geht um Verhaltenskompatibilität.
- Das Enten-Beispiel: Wenn es aussieht wie eine Ente und quakt wie eine Ente, aber Batterien braucht, hast du wahrscheinlich die falsche Abstraktion gewählt.
- Technisches Beispiel: Du hast eine Klasse
Vogelmit der Methodefliegen(). Nun leitest du die KlassePinguinvonVogelab. Da Pinguine nicht fliegen können, wirft die Methodefliegen()beim Pinguin einen Fehler oder macht nichts. Ein Programmteil, der eine Liste von Vögeln durchgeht und alle fliegen lassen will, wird beim Pinguin abstürzen. Der Pinguin ist kein vollwertiger Ersatz für den Vogel in diesem Kontext. - Lösung: Die Vererbungshierarchie ist falsch.
fliegen()gehört nicht in die BasisklasseVogel, sondern in ein InterfaceFlugfähig, das nur vonTaubeundAdler, aber nicht vonPinguinimplementiert wird.
class Vogel:
def essen(self):
pass
class Flugfaehig:
def fliegen(self):
pass
class Adler(Vogel, Flugfaehig):
def essen(self):
print("Der Adler isst.")
def fliegen(self):
print("Der Adler fliegt.")
class Taube(Vogel, Flugfaehig):
def essen(self):
print("Die Taube isst.")
def fliegen(self):
print("Die Taube fliegt.")
class Pinguin(Vogel):
def essen(self):
print("Der Pinguin isst.")Interface Segregation Principle (ISP)
Das ISP fordert: "Clients sollten nicht gezwungen werden, von Interfaces abhängig zu sein, die sie nicht nutzen."
Es ist besser, viele kleine, spezifische Schnittstellen zu haben als eine große, "allmächtige" Schnittstelle.
- Problem: Du hast ein Interface
Multifunktionsgeraetmit den Methodendrucken(),scannen()undfaxen(). Ein einfacher Drucker, der dieses Interface implementiert, müsste auchscannen()undfaxen()implementieren (z.B. leer lassen), obwohl er das gar nicht kann. Jede Änderung an der Fax-Funktion im Interface würde ein Neu-Kompilieren des Druckers erfordern. - Lösung: Teile das Interface auf in
Druckbar,ScannbarundFaxbar. Der einfache Drucker implementiert nurDruckbar. Das Multifunktionsgerät implementiert alle drei.
class Druckbar:
def drucken(self):
pass
class Scannbar:
def scannen(self):
pass
class Faxbar:
def faxen(self):
pass
class EinfacherDrucker(Druckbar):
def drucken(self):
print("Drucken...")
class Multifunktionsgeraet(Druckbar, Scannbar, Faxbar):
def drucken(self):
print("Drucken...")
def scannen(self):
print("Scannen...")
def faxen(self):
print("Faxen...")Dependency Inversion Principle (DIP)
Das DIP besagt: "Abhängigkeiten sollten auf Abstraktionen beruhen, nicht auf Konkretisierungen."
Hochrangige Module (die wichtige Geschäftslogik enthalten) sollten nicht direkt von niederrangigen Modulen (wie Datenbanktreibern oder Web-APIs) abhängen. Beide sollten von Schnittstellen (Interfaces) abhängen.
- Problem: Eine Klasse
OnlineShoperzeugt in ihrem Code direkt ein ObjektMySQLDatenbank. Wenn du nun auf eine andere Datenbank wechseln willst, musst du den Code desOnlineShopändern. - Lösung: Der
OnlineShopdefiniert nur, was er braucht: ein InterfaceDatenbank. Welche konkrete Datenbank genutzt wird, wird von außen "hineingegeben" (dies nennt man Dependency Injection). Jetzt ist es dem Shop egal, ob die Daten in MySQL, PostgreSQL oder einer Textdatei liegen, solange das Interface stimmt.
class StupidOnlineShop:
def __init__(self):
self.db = MySQLDatenbank() # Feste Abhängigkeit!
class DIPOnlineShop:
def __init__(self, datenbank: IDatenbank): # Abhängigkeit von Abstraktion
self.db = datenbankLernziele
- das Konzept der SOLID-Prinzipien erklären, indem die fünf Prinzipien (Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, Dependency Inversion) als Leitlinien für objektorientiertes Design zur Verbesserung von Wartbarkeit, Erweiterbarkeit und Wiederverwendbarkeit vorgestellt werden.
- das Single Responsibility Principle (SRP) interpretieren, indem analysiert wird, wie die Beschränkung einer Klasse auf einen einzigen Verantwortungsbereich (einen Grund zur Änderung) die Kohäsion erhöht und die Kopplung verringert.
- das Liskov Substitution Principle (LSP) interpretieren, indem erklärt wird, dass Objekte einer Basisklasse durch Objekte ihrer abgeleiteten Klassen ersetzt werden können müssen, ohne die Korrektheit des Programms zu beeinträchtigen (Verhaltenskompatibilität).
- das Open/Closed Principle (OCP) veranschaulichen, indem gezeigt wird, wie Software-Entitäten (Klassen, Module, Funktionen) offen für Erweiterungen, aber geschlossen für Modifikationen gestaltet werden können (z.B. durch Vererbung oder Polymorphie), um bestehenden Code vor Änderungen zu schützen.
- das Interface Segregation Principle (ISP) veranschaulichen, indem die Vorteile von spezifischen, kleinen Schnittstellen gegenüber großen, allgemeinen Schnittstellen dargestellt werden, um Clients nicht von Methoden abhängig zu machen, die sie nicht nutzen.
- das Dependency Inversion Principle (DIP) interpretieren, indem analysiert wird, wie die Abhängigkeit von Abstraktionen (Interfaces) statt von konkreten Implementierungen die Flexibilität erhöht und die Kopplung zwischen Modulen verringert.
Vertiefe dein Wissen!
Du hast die Grundlagen verstanden? Perfekt! In unserer App findest du interaktive Übungen, praktische Projekte und erweiterte Inhalte zu SOLID-Prinzipien.
Ab 5€/Monat • Kostenloser Test verfügbar