Abschlussprojekt: Eine gemeinschaftliche Leihbibliothek · Übung
Polymorphes Verhalten unterstützen
Lektion 5 ließ absichtlich etwas falsch:
LOAN_PERIOD_DAYS = 14
Jeder Gegenstand bekommt zwei Wochen. Ein Roman und ein Brettspiel sind nicht dieselbe Art von ausleihbarem Gegenstand. Jede echte Bibliothek weiß das. Jetzt korrigierst du es. Wie du es korrigierst, ist das Thema dieser Lektion.
Die abzulehnende Version
Hier ist die offensichtliche Reparatur. Es lohnt sich, sie auszuschreiben, damit du ihren Fehler siehst:
if isinstance(item, Book):
period = 21
elif isinstance(item, Game):
period = 7
Sie funktioniert. Sie platziert aber auch eine Tatsache über Bücher in Library. Eine dritte Gegenstandsart hinzuzufügen bedeutet dadurch eine Änderung an checkout statt nur eine neue Klasse.
Kapitel 9 benannte dieses Warnzeichen: Eine Kette von Typprüfungen ist meistens ein Objekt, das „Was bist du?“ fragt, obwohl es das andere Objekt zum Handeln auffordern sollte. Das Wissen, wie lange ein Spiel behalten werden darf, gehört zu Game, denn es ist eine Tatsache über Spiele.
Den Gegenstand fragen
class Book:
def loan_period_days(self):
return 21
class Game:
def loan_period_days(self):
return 7
Und checkout hört auf zu entscheiden:
item = self._items[item_id]
due_day = checkout_day + item.loan_period_days()
Eine Zeile, und jede Typprüfung ist verschwunden. Library weiß nicht mehr, dass es so etwas wie ein Buch gibt. Es weiß, dass es Gegenstände hält und dass ein Gegenstand sagen kann, wie lange er behalten werden darf.
Füge nächstes Jahr eine dritte Gegenstandsart hinzu, und checkout ändert sich nicht. Das ist die wünschenswerte Eigenschaft, die die Version mit isinstance nicht bieten kann.
Keine Basisklasse erforderlich
Kapitel 9 war hier sorgfältig, also ist diese Lektion es auch.
Book und Game teilen keine ausdrücklich definierte Elternklasse. Sie sind nicht durch eine eigene Vererbungshierarchie verbunden. Austauschbar macht sie hier, dass beide loan_period_days() und describe() beantworten. Library fragt nach nichts anderem.
Das ist Duck Typing, und es genügt. Eine gemeinsame Basisklasse brächte nur etwas, wenn es tatsächlich Verhalten zu teilen gäbe. Das gibt es momentan nicht: Die beiden Implementierungen haben außer ihren Namen nichts gemeinsam.
Die Schnittstellenzusage stimmt zu und sagt das ausdrücklich:
Vererbung ist optional.
BookundGamekönnen eine kleine Basisklasse teilen oder einfach durch Duck Typing dieselben öffentlichen Operationen erfüllen.
Bevorzuge die Version mit weniger zusätzlicher Struktur, bis diese Struktur ihren Platz rechtfertigt.
Book und Game teilen keine eigene Basisklasse, trotzdem behandelt Library sie gleich. Warum funktioniert das?
Die zweite polymorphe Methode
describe() ist die andere Operation, die laut Zusage jeder Gegenstand unterstützen muss. Hier wird der Unterschied zwischen den beiden Typen für eine Person sichtbar statt nur für den Code:
Solaris by Lem
Carcassonne (up to 5 players)
Derselbe Aufruf, ein anderer Satz. Kein Aufrufer muss wissen, welche Art von Gegenstand er hält. Genau das ermöglicht einen Bericht über einen gemischten Katalog.
Was du jetzt baust
Lektion 2 gab Book und Game bereits beide Operationen. Diese Lektion widmet sich der anderen Seite dieser Vereinbarung: Library soll sie tatsächlich nutzen.
Du fügst deiner gespeicherten Klasse Library die Methode describe_catalog() hinzu. Sie meldet die verfügbaren Gegenstände in Katalogreihenfolge, indem sie jeden nach seiner Beschreibung fragt. Die alternative Startvorlage zeigt zum Vergleich eine Version mit isinstance. Beim Öffnen dieser Lektion bleibt deine gespeicherte Datei aber erhalten; die neue Methode wird nicht automatisch eingefügt. Auch checkout muss sich ändern: Es wendet derzeit eine einzige Zahl auf alles an.
Beides wird zu einem einzigen Aufruf am Gegenstand. Wenn du fertig bist, importiert oder verwendet library.py die Klassen Book und Game nicht mehr. Genau das solltest du prüfen.
Aufgabe
Entferne die Typprüfungen aus library.py.
Füge deiner bestehenden Klasse Library die Methode describe_catalog(self) hinzu. Sie gibt Beschreibungen der Gegenstände aus available_items() in derselben Reihenfolge zurück, indem sie für jeden Gegenstand describe() aufruft. Wenn du die alternative Startvorlage verwendest, ersetze deren Version mit Typprüfungen. Das Öffnen dieser Lektion überschreibt deine gespeicherte Datei nicht und fügt auch keine Methode darin ein.
Tue dann dasselbe mit checkout. Es gibt derzeit allem die Dauer LOAN_PERIOD_DAYS. Lass es den Fälligkeitstag berechnen, indem es den Gegenstand nach seiner eigenen Ausleihdauer fragt, und lösche die Konstante.
Am Ende sollte library.py im ausführbaren Code weder Book noch Game importieren oder verwenden und auch keine Gegenstandstypen untersuchen. Kommentare, die das Refactoring erklären, sind in Ordnung. Die Bibliothek hält Gegenstände und stellt ihnen Fragen. Welche Arten es gibt, ist nicht ihre Angelegenheit.