Abschlussprojekt: Eine gemeinschaftliche Leihbibliothek · Übung
Das zentrale Objekt mit seinem Verhalten implementieren
Für diese Methode existiert das ganze Projekt.
In checkout treffen alle Regeln der Schnittstellenzusage zusammen: Der Gegenstand muss existieren, das Mitglied muss existieren, der Gegenstand muss frei sein und das Mitglied muss unter seiner Ausleihgrenze liegen. Erst dann entsteht eine Ausleihe.
Zuerst ablehnen, zuletzt ändern
Die Reihenfolge innerhalb der Methode ist nicht beliebig.
def checkout(self, item_id, member_id, checkout_day):
if item_id not in self._items:
raise UnknownItemError(...)
if member_id not in self._members:
raise UnknownMemberError(...)
if item_id in self._loans:
raise ItemUnavailableError(...)
...
# only now does anything change
Jede Prüfung findet vor jeder Änderung statt. Das verdient eine ausdrückliche Regel, denn die Alternative scheitert auf schwer auffindbare Weise: Eine Methode speichert die Ausleihe, entdeckt danach, dass das Mitglied seine Grenze überschreitet, und löst eine Ausnahme aus. Der Aufrufer sieht die Ausnahme und nimmt vernünftigerweise an, dass nichts passiert ist. Die Bibliothek ist anderer Meinung.
Kapitel 5 nannte das, zusammengehörigen Zustand gemeinsam zu ändern, und gab denselben Rat aus der anderen Richtung: Ein Objekt sollte niemals in einem halb geänderten Zustand beobachtbar sein. Alle Ablehnungen zuerst zu erledigen ist der einfachste Weg dorthin.
Vier Fragen, vier unterschiedliche Fehler
Die vorgegebene errors.py liefert dir eine kleine Familie:
LibraryError
├── UnknownItemError
├── UnknownMemberError
├── ItemUnavailableError
└── LoanLimitReachedError
Kapitel 12 plädierte für genau diese Form. Ein Aufrufer, der nur wissen möchte, dass etwas schiefging, fängt LibraryError ab. Ein Aufrufer, der eine Benutzerschnittstelle baut, möchte in einem Fall sagen „Das ist schon ausgeliehen, versuche es nächste Woche“ und in einem anderen „Du hast schon zu viel ausgeliehen“. Das geht nur, wenn die vier Fälle unterscheidbar sind.
Widerstehe der Versuchung, sie zusammenzufassen. „Ausleihe nicht möglich“ ist eine Meldung für vier Situationen. Wer sie liest, muss raten, welche gemeint ist.
Zählen, was ein Mitglied ausgeliehen hat
Die Ausleihgrenze benötigt eine Anzahl. Diese Anzahl wird nirgendwo gespeichert. Das ist Absicht: Lektion 2 hielt Ausleihen von Member fern, damit es genau eine Stelle gibt, die sie kennt.
Also zählt die Bibliothek:
in_hand = 0
for loan in self._loans.values():
if loan.member_id == member_id:
in_hand = in_hand + 1
Die Anzahl abzuleiten statt zu speichern verhindert, dass sie veraltet. Ein gespeicherter Zähler ist eine zweite maßgebliche Angabe, die bei jeder Ausleihe und jeder Rückgabe angepasst werden muss. Irgendwann vergisst ein Ablauf das.
Warum die Ausleihen eines Mitglieds aus self._loans zählen, statt einen laufenden Gesamtwert auf Member zu halten?
Die Ausleihdauer, vorerst
Eine Ausleihe braucht einen Fälligkeitstag. Deshalb muss checkout entscheiden, wie lange der Gegenstand behalten werden darf.
Gib ihm vorerst eine einzige Ausleihdauer als Konstante oben in der Datei:
LOAN_PERIOD_DAYS = 14
Du weißt bereits, dass das falsch ist. Ein Nachschlagewerk und ein Brettspiel erhalten nicht dieselben zwei Wochen. In Lektion 7 wird das richtig gelöst. Zuerst die einfache Version zu schreiben ist Absicht: Sie bringt die Methode von Anfang bis Ende zum Funktionieren und legt die Entscheidung an eine offensichtliche Stelle. Sie später zu verschieben wird damit eine kleine, sichere Änderung statt einer aufwendigen Suche.
Einen Gegenstand zurückgeben
return_item(item_id) ist das Gegenstück. Es gibt eine Ablehnung, wenn der Gegenstand gerade nicht ausgeliehen ist, und eine Aufgabe: die Ausleihe entfernen und zurückgeben, damit der Aufrufer melden kann, was zurückgegeben wurde.
Durch das Entfernen wird der Gegenstand wieder verfügbar. Es gibt kein separates Kennzeichen „verfügbar“, und das sollte es auch nicht: Ein solches Kennzeichen und ein Ausleihdatensatz sagen dasselbe auf zwei Arten. Lektion 6 zeigt, wie sich Verfügbarkeit allein aus den Ausleihen beantworten lässt.
Aufgabe
Implementiere checkout und return_item in library.py.
Füge oben in der Datei LOAN_PERIOD_DAYS = 14 hinzu und importiere neben LibraryError die vier spezifischen Ausnahmen.
checkout(item_id, member_id, checkout_day) lehnt in dieser Reihenfolge ab, bevor es etwas verändert:
Eine unbekannte
item_idlöstUnknownItemError(item_id)aus.Eine unbekannte
member_idlöstUnknownMemberError(member_id)aus.Ein bereits ausgeliehener Gegenstand löst unter Verwendung seiner bestehenden Ausleihe
ItemUnavailableError(item_id, due_day)aus.Ein Mitglied, das seine
loan_limitbereits erreicht hat, löst unter Verwendung dieser GrenzeLoanLimitReachedError(member_id, limit)aus.
Übergib diese Werte getrennt, statt hier eine Meldung zu bauen. Die vorgegebenen Ausnahmeklassen nehmen sie bereits entgegen. Lektion 8 gibt ihnen benannte Attribute und lesbare Meldungen. Importiere Loan aus models.
Andernfalls erstellt die Methode einen Loan, der LOAN_PERIOD_DAYS nach checkout_day fällig ist, speichert ihn unter der Gegenstands-ID und gibt ihn zurück.
return_item(item_id) löst LibraryError aus, wenn dieser Gegenstand gerade nicht ausgeliehen ist. Andernfalls entfernt die Methode die Ausleihe und gibt sie zurück.
Zähle die aktuellen Ausleihen eines Mitglieds anhand der gespeicherten Ausleihen. Füge Member keinen Zähler hinzu.