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.
| Number | Muster & Beschreibung | Enthä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. |
|
| 2 | Strukturmuster Konzentrieren sich auf die Zusammensetzung und Zusammenarbeit von Klassen und Objekten und helfen uns, flexible, wiederverwendbare und erweiterbare Systemstrukturen zu schaffen. |
|
| 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. |
|
| 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. |
|
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
_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
# 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 definiertEmailNotificationundSMSNotificationist die konkrete ImplementierungNotificationFactoryist 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
# 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
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 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")