Ein Ticketautomat, der viele Fälle kennt
Ein Entwurfsmuster ist eine bewährte Lösung für ein Problem, das im Entwurf von Programmen immer wiederkehrt. Es hat einen Namen, damit Entwickelnde in einem Wort sagen können, was sie meinen.
Ein Ticketautomat erzeugt Fahrkarten, rechnet je nach Tarif und bietet Zusatzleistungen an. Drei Probleme, drei Muster: Fabrik, Strategie und Dekorierer.
Am Ende dieser Lektion
Notiz
Ich kann zu einem Problem eine Fabrik, eine Strategie oder einen Dekorierer wählen und anwenden.
Schlüsselwörter
| Begriff | Was er bedeutet | Beispiel |
|---|---|---|
| Entwurfsmuster | eine benannte Lösung für ein wiederkehrendes Entwurfsproblem | |
| Fabrik | ein Ort, der entscheidet, welches Objekt entsteht | ticket_fuer("tag") |
| Strategie | ein austauschbarer Rechenweg als eigenes Objekt | |
| Dekorierer | ein Objekt, das ein anderes umhüllt und etwas ergänzt | |
| Umhüllen | ein Objekt als Teil in ein anderes stecken |
Die Zellen, die Lernende ausfüllen, bleiben in dieser Vorschau leer.
Eine Fabrik entscheidet, welches Objekt entsteht
Das Problem: Ein Programm bekommt einen Text wie "tag" und braucht dazu das richtige Objekt. Steht die Entscheidung an zehn Stellen, ändern Sie bei einer neuen Art zehn Stellen.
Bei der Fabrik liegt die Entscheidung in einer einzigen Funktion. Alle anderen rufen nur ticket_fuer(art) auf und arbeiten mit dem Objekt, das zurückkommt.
Eine Fabrik für Fahrkarten
class Einzelfahrt:
def preis_cent(self):
return 290
class Tageskarte:
def preis_cent(self):
return 780
def ticket_fuer(art):
if art == "einzel":
return Einzelfahrt()
if art == "tag":
return Tageskarte()
raise ValueError("Unbekannte Art: " + art)
for art in ["einzel", "tag"]:
ticket = ticket_fuer(art)
print(art, ticket.preis_cent())
# Fügen Sie eine dritte Art mit eigenem Preis hinzu.
Eine Strategie macht den Rechenweg austauschbar
Das Problem: Ein Parkhaus rechnet mal pro Stunde, mal mit Tagespauschale. Eine if-Kette in kassieren wächst mit jedem neuen Tarif.
Bei der Strategie wird jeder Rechenweg eine eigene kleine Klasse mit derselben Methode. Das Parkhaus hält einen davon als Teil und ruft ihn auf. Das ist Komposition mit einem Zweck.
Zwei Tarife, ein Parkhaus
class Stundentarif:
def preis_cent(self, stunden):
return stunden * 200
class Tagespauschale:
def preis_cent(self, stunden):
return 900
class Parkhaus:
def __init__(self, tarif):
self.tarif = tarif
def kassieren(self, stunden):
return self.tarif.preis_cent(stunden)
haus = Parkhaus(Stundentarif())
print(haus.kassieren(3))
haus.tarif = Tagespauschale()
print(haus.kassieren(3))
# Rechnen Sie mit 6 Stunden und vergleichen Sie beide Tarife.
Die drei Muster im Überblick
| Muster | Problem | Idee | Beispiel |
|---|---|---|---|
| Fabrik | Wer entscheidet, welches Objekt entsteht? | Eine Funktion wählt die Klasse | ticket_fuer("tag") |
| Strategie | Der Rechenweg soll wechseln | Jeder Weg ist ein Objekt mit gleicher Methode | Stundentarif, Tagespauschale |
| Dekorierer | Eine Zusatzleistung ohne neue Unterklasse | Ein Objekt umhüllt ein anderes | MitFahrrad(Einzelfahrt()) |
Die Zellen, die Lernende ausfüllen, bleiben in dieser Vorschau leer.
Ein Dekorierer umhüllt ein Objekt und ergänzt es
Das Problem: Zu einer Fahrkarte kommen Zusatzleistungen wie ein Fahrrad. Für jede Mischung eine Unterklasse zu schreiben, wird schnell unübersichtlich.
Der Dekorierer hat dieselben Methoden wie das Objekt, das er umhüllt. Er ruft es auf und ergänzt das Ergebnis. Das hat nichts mit der @-Schreibweise von Python für Funktionen zu tun. Hier geht es um Objekte.
Eine Fahrkarte mit Fahrrad
class Einzelfahrt:
def preis_cent(self):
return 290
def text(self):
return "Einzelfahrt"
class MitFahrrad:
def __init__(self, ticket):
self.ticket = ticket
def preis_cent(self):
return self.ticket.preis_cent() + 150
def text(self):
return self.ticket.text() + " mit Fahrrad"
ticket = MitFahrrad(Einzelfahrt())
print(ticket.text(), ticket.preis_cent())
# Umhüllen Sie das Ergebnis noch einmal mit MitFahrrad.
Ein häufiger Fehler
Warnung
Viele suchen sich zuerst ein Muster aus und biegen dann das Problem zurecht. Gehen Sie umgekehrt vor: Benennen Sie das Problem, dann prüfen Sie, ob ein Muster passt. Eine Entscheidung mit zwei festen Fällen braucht keine Strategie. Ein einfaches if genügt.
Zwei Hüllen in verschiedener Reihenfolge
Im Lab bauen Sie einen Text und zwei Dekorierer: Doppelt wiederholt den Text, Ausruf hängt ein Ausrufezeichen an.
Dann umhüllen Sie in beiden Reihenfolgen. Die Ausgabe zeigt, dass die Reihenfolge zählt: erst jaja!, dann ja!ja!.
Den Ordner anlegen
mkdir -p ~/oop-laborHier angezeigt, nicht ausgeführt. Erstellen Sie ein kostenloses Konto, um „Ausführen“ zu drücken und ihn im Terminal Ihres eigenen Labs einzugeben.
Hineingehen
cd ~/oop-laborHier angezeigt, nicht ausgeführt. Erstellen Sie ein kostenloses Konto, um „Ausführen“ zu drücken und ihn im Terminal Ihres eigenen Labs einzugeben.
Den Text und zwei Hüllen schreiben
printf 'class Text:\n def __init__(self, inhalt):\n self.inhalt = inhalt\n\n def lesen(self):\n return self.inhalt\n\nclass Doppelt:\n def __init__(self, teil):\n self.teil = teil\n\n def lesen(self):\n return self.teil.lesen() + self.teil.lesen()\n\nclass Ausruf:\n def __init__(self, teil):\n self.teil = teil\n\n def lesen(self):\n return self.teil.lesen() + "!"\n\nprint(Ausruf(Doppelt(Text("ja"))).lesen())\nprint(Doppelt(Ausruf(Text("ja"))).lesen())\n' > huellen.pyHier angezeigt, nicht ausgeführt. Erstellen Sie ein kostenloses Konto, um „Ausführen“ zu drücken und ihn im Terminal Ihres eigenen Labs einzugeben.
Das Programm starten
python3 huellen.pyHier angezeigt, nicht ausgeführt. Erstellen Sie ein kostenloses Konto, um „Ausführen“ zu drücken und ihn im Terminal Ihres eigenen Labs einzugeben.
Die Reihenfolge der Hüllen ändert das Ergebnis
Im ersten Fall wird zuerst verdoppelt, dann kommt das Ausrufezeichen: jaja!. Im zweiten Fall hat jede Hälfte ein Ausrufezeichen: ja!ja!.
Jede Hülle sieht nur das Objekt direkt in ihr. Deshalb lassen sich Hüllen frei kombinieren, solange alle lesen anbieten.