Abschlussprojekt: Eine gemeinschaftliche Leihbibliothek · Übung
Das Wertobjekt Loan implementieren
Book, Game und Member sind Dinge mit einer Identität. Zwei Mitglieder namens Mina sind zwei Personen. Die Bibliothek unterscheidet sie anhand ihrer ID.
Bei einer Ausleihe ist das anders.
Was eine Ausleihe zu einem Wert macht
Eine Ausleihe ist ein Datensatz: dieser Gegenstand, an dieses Mitglied, ab diesem Tag, zurückzugeben an jenem Tag. Zwei Ausleihen, bei denen alle vier Angaben gleich sind, sind keine zwei Ausleihen. Sie sind dieselbe Tatsache, zweimal aufgeschrieben.
Kapitel 8 hatte einen Namen für solche Objekte: Wertobjekte. Sie sind vollständig durch ihren Inhalt definiert, werden anhand ihrer Inhalte verglichen und ändern sich nach ihrer Erstellung nicht. Genau das ist eine Ausleihe. Deshalb verlangt die Schnittstellenzusage hier eine Dataclass und bei Book nicht.
from dataclasses import dataclass
@dataclass(frozen=True)
class Loan:
item_id: str
member_id: str
checkout_day: int
due_day: int
frozen=True erfüllt eine echte Aufgabe. Könnte eine Ausleihe direkt bearbeitet werden, könnte jeder Code mit Zugriff darauf stillschweigend das Rückgabedatum ändern, ohne dass die Bibliothek davon erfährt. Eingefroren bedeutet, dass eine Ausleihe nur durch Ersetzen geändert werden kann. Das tut die Bibliothek bewusst.
Du bekommst __init__, ein lesbares __repr__ und Wertgleichheit ohne zusätzliche Arbeit. Alle drei sind später wichtig: Die Repräsentation erscheint bei einem fehlgeschlagenen Test, und die Gleichheit ermöglicht einen Test wie assert loan in library.active_loans().
IDs statt Objekte speichern
Schau dir an, was die Ausleihe enthält: item_id und member_id, nicht Book und Member.
Das ist eine bewusste Entscheidung, die du verstehen solltest, denn die andere Version ist verlockend. Die Objekte zu halten würde erlauben, loan.item.title zu schreiben, statt den Gegenstand nachzuschlagen.
Zwei Gründe sprechen dagegen. Die Bibliothek verwaltet bereits den Katalog. Würde eine Ausleihe den Gegenstand halten, läge dieselbe Beziehung an einer zweiten Stelle, genau wovor Kapitel 6 warnte. Außerdem wäre eine eingefrorene Ausleihe mit einem veränderlichen Member nur oberflächlich eingefroren: Die Referenz in der Ausleihe ließe sich nicht neu zuweisen, das enthaltene Mitglied könnte sich aber trotzdem ändern. Eine Ausleihe aus IDs ist bis in die enthaltenen Werte unveränderlich.
Der Preis ist real: Um den Titel einer Ausleihe zu melden, musst du die Bibliothek fragen. Das ist die richtige Richtung für diese Abhängigkeit.
Loan hat frozen=True. Was wäre weiterhin veränderlich, wenn es ein Member-Objekt statt einer member_id enthielte?
Ein kleines Stück Verhalten
Ein Wertobjekt darf Fragen über sich selbst beantworten. Kapitel 8 erklärte, dass ein Wertobjekt mit nichts als Daten oft eine verpasste Gelegenheit ist.
is_overdue(on_day) ist hier die naheliegende Frage:
def is_overdue(self, on_day):
return on_day > self.due_day
Beachte, dass die Methode den Tag erhält, statt eine Uhr abzufragen, genau wie overdue_loans(on_day). Beachte auch den Ort der Regel: Ob eine Ausleihe überfällig ist, ist eine Tatsache über die Ausleihe. Der Vergleich gehört deshalb auf Loan, statt überall neu geschrieben zu werden, wo er benötigt wird.
Überfällig bedeutet >, nicht >=. Ein an Tag 20 fälliger Gegenstand ist an Tag 20 noch nicht überfällig, an Tag 21 aber schon. Der Unterschied ist ein Tastendruck. Genau solche Entscheidungen verdienen einen Test an der Grenze, wie Lektion 5 aus Kapitel 13 erklärte.
Aufgabe
Füge Loan zu models.py hinzu. Lass Book, Game und Member unverändert.
Mache daraus eine eingefrorene Dataclass mit vier Feldern in dieser Reihenfolge: item_id, member_id, checkout_day, due_day. Die ersten beiden sind Strings, die letzten beiden einfache ganzzahlige Tagesnummern.
Gib ihr eine Methode, is_overdue(on_day), die zurückgibt, ob die Ausleihe an diesem Tag überfällig ist. Eine an Tag 20 fällige Ausleihe ist an Tag 20 nicht überfällig.
Speichere IDs, keine Book- oder Member-Objekte. Die Bibliothek verwaltet diese. Eine eingefrorene Ausleihe, die ein veränderliches Objekt hält, wäre nicht vollständig eingefroren.