SOLID Prinzipien in Python

You are here:

Die SOLID-Prinzipien sind ein Satz von Designprinzipien für die objektorientierte Programmierung, die entwickelt wurden, um den Code wartbarer, erweiterbarer und leichter verständlich zu gestalten. Die SOLID-Prinzipien bestehen aus fünf Hauptprinzipien:

Single Responsibility Principle (SRP)

Jede Klasse sollte nur eine einzige Verantwortung haben. Das bedeutet, dass jede Klasse nur einen bestimmten Teil der Funktionalität abdecken sollte und nicht mehrere, unterschiedliche Aufgaben übernehmen sollte. Hier ist ein Beispiel:

# Nicht SOLID

class User:

    def save_user(self, user):
        # Code zum Speichern des Benutzers in der Datenbank
        pass

    def send_email(self, email):
        # Code zum Senden einer E-Mail
        pass

# SOLID
class UserRepository:
    def save_user(self, user):
        # Code zum Speichern des Benutzers in der Datenbank
        pass

class EmailService:
    def send_email(self, email):
        # Code zum Senden einer E-Mail
        pass

Open/Closed Principle (OCP)

Klassen sollten offen für Erweiterungen, aber geschlossen für Modifikationen sein. Das bedeutet, dass du neue Funktionen hinzufügen können solltest, ohne den bestehenden Code ändern zu müssen. Eine Möglichkeit, dies in Python umzusetzen, ist die Verwendung von abstrakten Basisklassen und Vererbung:

from abc import ABC, abstractmethod

class Animal(ABC):
    @abstractmethod
    def speak(self):
        pass

class Dog(Animal):
    def speak(self):
        return "Woof!"

class Cat(Animal):
    def speak(self):
        return "Meow!"

Liskov Substitution Principle (LSP)

Unterklassen sollten in der Lage sein, ihre Basisklassen ohne Beeinträchtigung der Funktionalität zu ersetzen. In Python bedeutet das, dass die Methoden in Unterklassen die gleichen Signaturen wie die Methoden in ihren Basisklassen haben sollten:

class Bird:
    def fly(self):
        pass

class Penguin(Bird):
    def fly(self):
        raise NotImplementedError("Penguins can't fly")

# Hier verletzen wir das LSP, weil `Penguin` nicht als Ersatz für `Bird` verwendet werden kann.

Interface Segregation Principle (ISP)

Klassen sollten nicht gezwungen sein, Interfaces zu implementieren, die sie nicht verwenden. In Python gibt es keine expliziten Interfaces wie in anderen Programmiersprachen, aber das Prinzip kann durch die Verwendung von abstrakten Basisklassen und Mixins angewendet werden:

from abc import ABC, abstractmethod

class Flyable(ABC):
    @abstractmethod
    def fly(self):
        pass

class Swimmable(ABC):
    @abstractmethod
    def swim(self):
        pass

class Duck(Flyable, Swimmable):
    def fly(self):
        # Implementierung für Fliegen
        pass

    def swim(self):
        # Implementierung für Schwimmen
        pass

Dependency Inversion Principle (DIP)

Abhängigkeiten sollten gegen Abstraktionen und nicht gegen konkrete Implementierungen programmiert werden. Das bedeutet, dass deine Klassen von abstrakten Klassen oder Interfaces abhängen sollten, anstatt direkt von konkreten Implementierungen abhängig zu sein. In Python kann dies durch die Verwendung von abstrakten Basisklassen oder Duck-Typing erreicht werden:

from abc import ABC, abstractmethod

class Database(ABC):
    @abstractmethod
    def connect(self):
        pass

class MySQLDatabase(Database):
    def connect(self):
        # Verbindung zu einer MySQL-Datenbank herstellen
        pass

class PostgreSQLDatabase(Database):
    def connect(self):
        # Verbindung zu einer PostgreSQL-Datenbank herstellen
        pass

class Application:
    def __init__(self, database: Database):
        self.database = database

app = Application(MySQLDatabase())

In diesem Beispiel hängt die `Application`-Klasse von der abstrakten Basisklasse `Database` ab, anstatt direkt von `MySQLDatabase` oder `PostgreSQLDatabase`. Das ermöglicht es uns, die Datenbankimplementierung auszutauschen, ohne die `Application`-Klasse ändern zu müssen.

Indem du die SOLID-Prinzipien in deinen Python-Anwendungen anwendest, kannst du sicherstellen, dass dein Code modular, erweiterbar und leicht verständlich bleibt. Um dies in der Praxis zu erreichen, denke daran, deinen Code in sinnvolle Module, Pakete und Klassen aufzuteilen und die Verantwortlichkeiten klar voneinander zu trennen. Achte darauf, dass du Abstraktionen verwendest, um lose Kopplungen zwischen den verschiedenen Teilen deiner Anwendung herzustellen. So bleibt dein Code auch bei wachsender Komplexität und neuen Anforderungen leicht zu warten und zu erweitern.