SOLID-Prinzipien

6 min 3 Abschnitte
Was du nach diesem Konzept kannst 5
  1. Du bist in der Lage, das Open/Closed Principle (OCP) und das Liskov Substitution Principle (LSP) zu veranschaulichen ,

    indem an Beispielen gezeigt wird, wie Software durch korrekte Vererbung und Polymorphie offen für Erweiterungen gestaltet werden kann, ohne bestehenden Code zu modifizieren oder das erwartete Verhalten der Basisklassen zu verfälschen.

  2. Du bist in der Lage, das Single Responsibility Principle (SRP) zu interpretieren ,

    indem analysiert wird, wie die Beschränkung einer Klasse auf einen einzigen Verantwortungsbereich die Kohäsion erhöht und die Kopplung verringert.

  3. Du bist in der Lage, das Interface Segregation Principle (ISP) zu veranschaulichen ,

    indem die Vorteile von spezifischen, kleinen Schnittstellen gegenüber großen, allgemeinen Schnittstellen anhand von Beispielen dargestellt werden.

  4. Du bist in der Lage, das Konzept der SOLID-Prinzipien zu erklären ,

    indem die fünf Prinzipien als Leitlinien für objektorientiertes Design zur Verbesserung von Wartbarkeit, Erweiterbarkeit und Wiederverwendbarkeit zusammenfassend dargestellt werden.

  5. Du bist in der Lage, das Dependency Inversion Principle (DIP) zu interpretieren ,

    indem analysiert wird, wie die Abhängigkeit von Abstraktionen statt von konkreten Implementierungen die Flexibilität erhöht und die Kopplung zwischen Modulen verringert.

Warum verbessern die SOLID-Prinzipien die Softwarearchitektur?

Vom funktionierenden zum wartbaren Code

Aus den Prinzipien für sauberen Code kennst du bereits Konzepte wie DRY (Don't Repeat Yourself) und KISS (Keep It Simple, Stupid). Während diese dir helfen, einzelne Methoden und Funktionen übersichtlich zu schreiben, setzen die SOLID-Prinzipien eine Ebene höher an: bei der Architektur deiner gesamten objektorientierten Anwendung.

Selbst wenn dein Code fehlerfrei läuft, kann ein schlechtes Design dazu führen, dass eine kleine Änderung an einer Stelle unerwartete Fehler an ganz anderen Stellen verursacht. Die SOLID-Prinzipien sind fünf bewährte Leitlinien, um Software so zu entwerfen, dass sie leicht verständlich, flexibel erweiterbar und einfach zu warten ist. Sie helfen dir, starren "Spaghetti-Code" zu vermeiden.

Die fünf Säulen der Softwarequalität

Das Akronym SOLID fasst fünf spezifische Entwurfsprinzipien zusammen. Ihr gemeinsames Ziel ist es, die Kohäsion (den logischen Zusammenhalt innerhalb eines Moduls) zu maximieren und die Kopplung (die Abhängigkeit zwischen verschiedenen Modulen) zu minimieren:

  1. Single Responsibility Principle (SRP)
  2. Open/Closed Principle (OCP)
  3. Liskov Substitution Principle (LSP)
  4. Interface Segregation Principle (ISP)
  5. Dependency Inversion Principle (DIP)

Wenn du diese Prinzipien verinnerlichst, schaffst du unabhängige Module, die dein Team auch Jahre später noch problemlos testen, anpassen und austauschen kann.

SOLID-Prinzipien — dec-software-engineering-software-architecture-programming-principles-solid-principles_page1.svg

Wie sorgen wir für klare Verantwortung und Erweiterbarkeit?

Single Responsibility Principle (SRP)

Stell dir eine Klasse Rechnung vor. Sie berechnet die Gesamtsumme, speichert die Daten in der Datenbank und generiert ein PDF für den E-Mail-Versand. Ändert sich das Datenbank-Passwort, musst du die Klasse anpassen. Ändert das Finanzamt die Vorgaben für das PDF-Format, musst du dieselbe Klasse anfassen. Diese Klasse hat eine geringe Kohäsion und eine zu hohe Kopplung an verschiedene externe Systeme.

Das Single Responsibility Principle (SRP) löst dieses Problem mit einer klaren Regel: "Eine Klasse sollte nur einen einzigen Grund haben, sich zu ändern."

Sie darf also nur für einen einzigen fachlichen oder technischen Verantwortungsbereich zuständig sein. Die Lösung für unser Beispiel ist die Aufteilung in spezialisierte Klassen:

  1. RechnungsBerechner: Kümmert sich ausschließlich um die Mathematik.
  2. RechnungsRepository: Übernimmt nur das Speichern in der Datenbank.
  3. RechnungsDrucker: Ist nur für das Erstellen des PDFs verantwortlich.

Open/Closed Principle (OCP)

Stell dir vor, du programmierst eine Gehaltsabrechnung für Festangestellte. Nun kommen Freelancer hinzu. Wenn du jetzt in deine Hauptklasse gehst und überall if (typ == Freelancer) einbaust, veränderst du bestehenden, bereits getesteten Code. Das birgt das Risiko, neue Fehler einzubauen.

Das Open/Closed Principle (OCP) fordert: "Software-Entitäten sollten offen für Erweiterung, aber geschlossen für Modifikation sein."

Du erreichst dies durch Polymorphie und Abstraktionen. Die Hauptklasse nutzt nur ein Interface Mitarbeitende. Um Freelancer zu unterstützen, erstellst du eine neue Klasse, die dieses Interface implementiert. Der Code der Abrechnungsmaschine bleibt unangetastet ("geschlossen"), kann aber neue Typen verarbeiten ("offen").

class Mitarbeitende:
    def berechne_gehalt(self) -> float:
        pass

class Festangestellte(Mitarbeitende):
    def berechne_gehalt(self) -> float:
        return 3000.0

class Freelancer(Mitarbeitende):
    def berechne_gehalt(self) -> float:
        return 2000.0

class AbrechnungsMaschine:
    # Diese Methode muss nie wieder geändert werden, 
    # auch wenn neue Mitarbeiter-Typen hinzukommen!
    def generiere_abrechnung(self, person: Mitarbeitende):
        print(f"Gehalt: {person.berechne_gehalt()}")
SOLID-Prinzipien — dec-software-engineering-software-architecture-programming-principles-solid-principles_page2.svg

Wie gestalten wir flexible Schnittstellen und Beziehungen?

Liskov Substitution Principle (LSP)

Stell dir vor, du hast eine Basisklasse Vogel mit der Methode fliegen(). Nun leitest du die Klasse Pinguin davon ab. Da Pinguine nicht fliegen, wirft die Methode beim Pinguin einen Fehler. Ein Programmteil, der eine Liste von Vögeln durchgeht und alle fliegen lassen will, wird beim Pinguin abstürzen.

Das Liskov Substitution Principle (LSP) besagt: "Objekte einer Basisklasse müssen durch Objekte ihrer abgeleiteten Klassen ersetzt werden können, ohne die Korrektheit des Programms zu beeinträchtigen."

Eine Unterklasse darf die Erwartungen an die Elternklasse nicht brechen oder deren Verhalten verfälschen. Die Lösung: Die Vererbungshierarchie war falsch. fliegen() gehört in ein separates Interface Flugfaehig, das nur von der Taube oder dem Adler, aber nicht vom Pinguin implementiert wird.

Interface Segregation Principle (ISP)

Stell dir ein Interface Multifunktionsgeraet mit den Methoden drucken(), scannen() und faxen() vor. Ein einfacher Drucker, der dieses Interface implementiert, müsste auch scannen() und faxen() als leere Methoden oder mit einer Fehlermeldung implementieren.

Das Interface Segregation Principle (ISP) fordert: "Clients sollten nicht gezwungen werden, von Interfaces abhängig zu sein, die sie nicht nutzen."

Große, allgemeine Schnittstellen führen zu unnötigen Abhängigkeiten. Es ist besser, viele kleine, spezifische Schnittstellen zu definieren. Teile das Interface auf in Druckbar, Scannbar und Faxbar. Der einfache Drucker implementiert nun nur noch Druckbar.

class Druckbar:
    def drucken(self): pass

class Scannbar:
    def scannen(self): pass

# Der einfache Drucker wird nicht mit ungenutzten Methoden belastet
class EinfacherDrucker(Druckbar):
    def drucken(self):
        print("Druckt Dokument...")

# Das Multifunktionsgerät kombiniert die kleinen Interfaces
class Multifunktionsgeraet(Druckbar, Scannbar):
    def drucken(self):
        print("Druckt Dokument...")
    def scannen(self):
        print("Scannt Dokument...")

Dependency Inversion Principle (DIP)

Wenn eine Klasse OnlineShop (hochrangige Geschäftslogik) in ihrem Code direkt ein konkretes Objekt MySQLDatenbank (niederrangiges Modul) erzeugt, sind beide stark gekoppelt. Willst du die Datenbank wechseln, musst du den Code des Shops umschreiben.

Das Dependency Inversion Principle (DIP) besagt: "Abhängigkeiten sollten auf Abstraktionen beruhen, nicht auf konkreten Implementierungen."

Der OnlineShop sollte nur ein abstraktes Interface IDatenbank kennen. Welche konkrete Datenbank genutzt wird, wird von außen an den Shop übergeben (Dependency Injection). Dadurch verringerst du die Kopplung massiv und erhöhst die Flexibilität: Dem Shop ist es nun völlig egal, ob die Daten in MySQL, PostgreSQL oder einer Textdatei liegen.

# FALSCH: Starke Kopplung an eine konkrete Implementierung
class SchlechterShop:
    def __init__(self):
        self.db = MySQLDatenbank() 

# RICHTIG: Abhängigkeit von einer Abstraktion (Interface)
class GuterShop:
    def __init__(self, datenbank: IDatenbank): 
        self.db = datenbank

Teste dein Wissen

Du diskutierst mit einer Kolleg:in über Code-Qualität. Warum sollt ihr bei der Entwicklung neben DRY und KISS auch noch die SOLID-Prinzipien anwenden?

Bereit für mehr?

Thema verstanden?

Teste dein Wissen interaktiv in unserer App. 7 Tage kostenlos, dann nur 5 € im Monat.