Python-Entwurfsmuster

Entwurfsmuster sind für häufige Probleme in der Softwareentwicklungwiederverwendbare Lösungen. Sie sind keine vollständigen Designs, die direkt in Code umgewandelt werden können, sondern vielmehrVorlagen oder Baupläne zur Lösung spezifischer Probleme。

Kernwert von Entwurfsmustern

Entwurfsmuster sind wie Standardbaupläne im Bauwesen und bieten die folgenden Vorteile für die Softwareentwicklung:

  • Verbessern die Wiederverwendbarkeit von Code: Vermeiden, das Rad neu zu erfinden
  • Verbessern die Wartbarkeit von Code: Machen die Codestruktur klarer
  • Fördern die Teamzusammenarbeit: Bieten eine einheitliche Programmiersprache und Denkweise
  • Codequalität verbessern: In der Praxis getestete Lösungen sind zuverlässiger

Analogie zu Entwurfsmustern im Alltag

Stellen Sie sich vor, Sie wollen ein Haus bauen:

  • Primitive Methode: Jedes Mal von Grund auf neu zu entwerfen ist fehleranfällig und ineffizient
  • Entwurfsmuster verwenden: Standard-Bauzeichnungen nutzen, die Standardgrundrisse für Wohnzimmer, Küche und Schlafzimmer kennen

Entwurfsmuster sind die "Standard-Bauzeichnungen" der Softwareentwicklung.

Arten von Entwurfsmustern

Laut dem klassischen Buch"Design Patterns - Elements of Reusable Object-Oriented Software" (chinesische Übersetzung: "Design Patterns: Reusable Object-Oriented Software Elements")Laut seiner Definition gibt es 23 klassische Entwurfsmuster. Diese Muster lassen sich nach ihrem Fokus in drei Hauptkategorien einteilen: Erzeugungsmuster, Strukturmuster und Verhaltensmuster. Darüber hinaus gibt es in der Entwicklung auf Unternehmensebene auch eine häufig verwendete Klasse von Architekturmustern.

NumberMuster & BeschreibungEnthält
1 Erzeugungsmuster
Wird verwendet, um Probleme bei der Objekterstellung zu lösen, indem die Instanziierungslogik gekapselt und konkrete Implementierungsdetails verborgen werden, wodurch das System beim Erstellen von Objekten flexibler und erweiterbarer wird.
  • Fabrikmuster
  • Abstraktes Fabrikmuster
  • Singleton-Muster
  • Builder-Muster
  • Prototyp-Muster
2 Strukturmuster
Konzentrieren sich auf die Zusammensetzung und Zusammenarbeit von Klassen und Objekten und helfen uns, flexible, wiederverwendbare und erweiterbare Systemstrukturen zu schaffen.
  • Adapter-Muster
  • Brücken-Muster
  • Filter-/Kriterienmuster
  • Kompositmuster
  • Dekorierer-Muster
  • Fassaden-Muster
  • Fliegengewicht-Muster
  • Proxy-Muster
3 Verhaltensmuster
Fokus auf Kommunikation und Zuweisung von Verantwortlichkeiten zwischen Objekten, Verbesserung von Systemflexibilität und Wartbarkeit durch Kapselung von Algorithmen, Zuständen oder Anfragelogik.
  • Chain-of-Responsibility-Muster
  • Befehlsmuster
  • Interpreter-Muster
  • Iterator-Muster
  • Vermittler-Muster
  • Memento-Muster
  • Beobachter-Muster
  • Zustands-Muster
  • Null-Objekt-Muster
  • Strategie-Muster
  • Schablonenmethoden-Muster
  • Besucher-Muster
4 Architekturmuster auf Unternehmensebene
Diese Muster werden häufig in geschichteten Architekturen großer Systeme verwendet und konzentrieren sich auf die Zusammenarbeit zwischen der Präsentationsschicht und der Geschäftslogikschicht.
  • MVC-Muster (Model-View-Controller-Muster)
  • Business-Delegate-Muster
  • Composite-Entity-Muster
  • Data-Access-Object-Muster
  • Front-Controller-Muster
  • Abfangfilter-Muster
  • Service-Locator-Muster
  • Transferobjekt-Muster

Das folgende Bild zeigt die Beziehungen und die hierarchische Struktur zwischen verschiedenen Entwurfsmustern:

设计模式之间的关系

Vorteile von Entwurfsmustern

  • Bieten eine einheitliche Designsprache, die es Entwicklern ermöglicht, Designabsichten schnell zu kommunizieren.
  • Bieten ausgereifte, wiederverwendbare Lösungen für häufige Probleme.
  • Reduzieren die Kopplung des Systems und verbessern die Wartbarkeit und Erweiterbarkeit des Codes.
  • Reduzieren redundantes Design und verbessern die Entwicklungseffizienz und Codequalität.
  • Helfen neuen Mitgliedern, die Systemarchitektur und Designphilosophie schnell zu verstehen.

Sechs Prinzipien der Entwurfsmuster

1. Open-Closed-Prinzip

Offen für Erweiterung, geschlossen für Modifikation. Software sollte sich an Änderungen anpassen, indem sie neue Funktionalität erweitert, anstatt vorhandenen Code zu ändern. Der Kern der Umsetzung dieses Prinzips liegt in der Verwendung von Abstraktionen (Schnittstellen oder Basisklassen), um stabiles Verhalten zu definieren.

2. Liskovsches Substitutionsprinzip

Unterklassen sollten in der Lage sein, jede Stelle zu ersetzen, an der eine Basisklasse auftritt. Vererbung ist nur sinnvoll, wenn abgeleitete Klassen die Basisklasse vollständig ersetzen können. Dieses Prinzip gewährleistet die Korrektheit der Polymorphie und ist eine wichtige Ergänzung zum Open-Closed-Prinzip.

3. Prinzip der Abhängigkeitsumkehr

Module höherer Ebene sollten nicht von Modulen niedrigerer Ebene abhängen; beide sollten von Abstraktionen abhängen. Konkrete Implementierungen sollten von Schnittstellen oder abstrakten Klassen abhängen statt direkt von konkreten Klassen. Dieses Prinzip macht das System einfacher zu erweitern und zu testen.

4. Schnittstellentrennungsprinzip

Eine Klasse sollte nicht von Schnittstellen abhängen, die sie nicht benötigt. Große Schnittstellen sollten in kleinere, spezifischere aufgeteilt werden, um die Kopplung zu verringern und die Flexibilität zu verbessern.

5. Gesetz von Demeter

Auch bekannt als das "Prinzip des geringsten Wissens": Ein Objekt sollte seine Interaktionen mit anderen Objekten minimieren. Mit anderen Worten: Eine Klasse sollte nur Objekte kennen, die direkt mit ihr selbst in Beziehung stehen, wodurch die Komplexität des Systems verringert wird.

6. Kompositions-Wiederverwendungsprinzip

Bevorzugen Sie die Verwendung von Kompositions- oder Aggregationsbeziehungen zur Wiederverwendung gegenüber Vererbung. Vererbung erzeugt starke Kopplung, während Komposition den flexiblen Austausch von Abhängigkeiten zur Laufzeit ermöglicht und so ein eleganteres strukturelles Design erreicht.


Gängige Beispiele für Entwurfsmuster in Python

Lassen Sie uns die Anwendung von Entwurfsmustern in Python anhand einiger konkreter Beispiele verstehen.

Singleton-Muster

Das Singleton-Muster stellt sicher, dass eine Klasse nur eine einzige Instanz hat, und bietet einen globalen Zugriffspunkt.

Beispiel

class DatabaseConnection:
    _instance = None
   
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            print("Neue Datenbankverbindung wird erstellt")
        return cls._instance
   
    def connect(self):
        print("Verbindung zur Datenbank wird hergestellt")

# Verwendungsbeispiel
db1 = DatabaseConnection()
db2 = DatabaseConnection()

print(f"Sind db1 und db2 dieselbe Instanz? {db1 is db2}")  # Ausgabe: True

Anwendungsszenarien:

  • Datenbank-Verbindungspool
  • Konfigurationsmanager
  • Logger

Fabrikmuster

Das Factory-Muster bietet eine Schnittstelle zum Erstellen von Objekten, sodass Unterklassen entscheiden können, welche Klasse instanziiert wird.

Beispiel

from abc import ABC, abstractmethod

# Abstraktes Produkt
class Notification(ABC):
    @abstractmethod
    def send(self, message: str):
        pass

# Konkretes Produkt
class EmailNotification(Notification):
    def send(self, message: str):
        print(f"E-Mail wird gesendet: {message}")

class SMSNotification(Notification):
    def send(self, message: str):
        print(f"SMS wird gesendet: {message}")

# Factory-Klasse
class NotificationFactory:
    @staticmethod
    def create_notification(notification_type: str) -> Notification:
        if notification_type == "email":
            return EmailNotification()
        elif notification_type == "sms":
            return SMSNotification()
        else:
            raise ValueError("Nicht unterstützter Benachrichtigungstyp")

# Verwendungsbeispiel
email = NotificationFactory.create_notification("email")
sms = NotificationFactory.create_notification("sms")

email.send("Ihre Bestellung wurde versandt")
sms.send("Verifizierungscode: 123456")

Code-Erklärung:

  • Notificationist eine abstrakte Basisklasse, die die Schnittstelle definiert
  • EmailNotificationundSMSNotificationist die konkrete Implementierung
  • NotificationFactoryist verantwortlich für das Erstellen konkreter Benachrichtigungsobjekte

Beobachter-Muster

Das Observer-Muster definiert eine Eins-zu-viele-Abhängigkeit zwischen Objekten. Wenn sich der Zustand eines Objekts ändert, werden alle Objekte benachrichtigt, die von ihm abhängen.

Beispiel

from abc import ABC, abstractmethod

# Observer-Schnittstelle
class Observer(ABC):
    @abstractmethod
    def update(self, message: str):
        pass

# Konkreter Observer
class EmailSubscriber(Observer):
    def __init__(self, name: str):
        self.name = name
   
    def update(self, message: str):
        print(f"{self.name} received email notification: {message}")

class SMSSubscriber(Observer):
    def __init__(self, name: str):
        self.name = name
   
    def update(self, message: str):
        print(f"{self.name} received SMS notification: {message}")

# Subjekt (Observable)
class NewsPublisher:
    def __init__(self):
        self._subscribers = []
   
    def subscribe(self, subscriber: Observer):
        self._subscribers.append(subscriber)
   
    def unsubscribe(self, subscriber: Observer):
        self._subscribers.remove(subscriber)
   
    def notify_subscribers(self, message: str):
        for subscriber in self._subscribers:
            subscriber.update(message)

# Verwendungsbeispiel
publisher = NewsPublisher()

# Abonnenten erstellen
alice = EmailSubscriber("Alice")
bob = SMSSubscriber("Bob")

# Neuigkeiten abonnieren
publisher.subscribe(alice)
publisher.subscribe(bob)

# Neuigkeiten veröffentlichen
publisher.notify_subscribers("Python 3.12 wurde veröffentlicht!")

# Abbestellen
publisher.unsubscribe(alice)
publisher.notify_subscribers("Dies ist eine Benachrichtigung, die nur Bob sehen kann")

Besondere Überlegungen zu Entwurfsmustern in Python

Pythons dynamische Natur

Als dynamische Sprache vereinfacht Python die Implementierung einiger Entwurfsmuster:

Beispiel

# Einfache Factory im Python-Stil
def create_payment(method):
    payment_methods = {
        'credit_card': CreditCardPayment,
        'paypal': PayPalPayment,
        'alipay': AlipayPayment
    }
    return payment_methods[method]()

# Direkt verwenden, keine komplexe Klassenhierarchie nötig
payment = create_payment('alipay')

Integrierte Unterstützung für das Decorator-Muster

Python verfügt über eine integrierte Decorator-Syntax, wodurch sich das Decorator-Muster eleganter implementieren lässt:

Beispiel

def log_execution_time(func):
    def wrapper(*args, **kwargs):
        import time
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"{func.__name__} Ausführungszeit: {end - start:.2f} Sekunden")
        return result
    return wrapper

@log_execution_time
def process_data(data):
    # Datenverarbeitung simulieren
    import time
    time.sleep(1)
    return f"Verarbeitete Daten: {data}"

# Verwendung
result = process_data("Beispieldaten")
Andere Erweiterungen