Slotproject: een uitleenbibliotheek voor de buurt · oefening
Teken de eerste samenwerking tussen objecten
Teken het plaatje voordat je de interessante methoden schrijft. Dat hoeft niet op papier, maar doe het ergens: de vorm die je nu kiest, bepaalt de ruimte waarin je in elke volgende les werkt.
Eén diagram, vier vakken
Library
| owns the catalog -> Book, Game
| owns the members -> Member
| owns the active loans -> Loan
Alle pijlen wijzen dezelfde kant op, en daarin zit het ontwerp. Library kent items, leden en uitleningen. Geen daarvan kent Library.
Hoofdstuk 6 gaf de reden: een verplichte afhankelijkheid terug in de andere richting maakt objecten lastiger om afzonderlijk te bouwen en te testen. Als de constructor van Member een bibliotheek vereiste, zou een test van de validatie van leden er ook een moeten aanleveren. In dit project heeft geen enkele bewerking op een lid die afhankelijkheid nodig.

Hoofdstuk 10 noemde deze vorm een afhankelijkheidsrichting. Die één kant op houden draagt er sterk aan bij dat een klein systeem werkbaar blijft. In het diagram betekent “ONE-WAY DEPENDENCIES” afhankelijkheden in één richting. “owns the collections and their rules” betekent dat de bibliotheek de collecties en hun regels beheert; “item” is een uitleenbaar voorwerp en “record” een gegevensrecord. De pijlen lopen vanuit de bibliotheek naar boeken, spellen, leden en uitleningen, zonder pijlen terug.
Een collectie beheren betekent ook haar regels beheren
Library bevat drie collecties. Het interessante is niet dat ze erin zitten, maar dat de klasse beslist wat erin mag.
Vanaf het begin gelden twee regels, allebei uit het contract:
dubbele identificatoren voor items en leden worden afgewezen
Een id moet precies één ding aanduiden, anders is checkout("b1", ...) dubbelzinnig en valt het hele contract uiteen. Daarom voegen add_item en register_member niet zomaar blind iets toe. Ze bewaken hun collecties.
Dat maakt ze de eerste echte methoden die het schrijven waard zijn en de eerste plek waar je de meegeleverde excepties gebruikt:
from errors import LibraryError
def add_item(self, item):
if item.item_id in self._items:
raise LibraryError(f"item {item.item_id!r} is already in the catalog")
...
Het argument uit hoofdstuk 12 geldt ongewijzigd: weigeren hoort bij de belofte van de methode en is geen ongelukje.
Waarom een dictionary met de id als sleutel
Het contract geeft je identificatoren. checkout(item_id, member_id, checkout_day) krijgt een string en moet het item vinden.
Met een lijst zou je elke keer moeten zoeken. Bovendien zou ook de controle “is deze id al in gebruik?” de lijst moeten doorlopen. Een dictionary met de id als sleutel drukt precies uit wat je bedoelt: één item per id. De controle op duplicaten gebruikt dezelfde opzoekbewerking als het ophalen.
self._items = {}
self._members = {}
self._loans = {}
De underscore vooraan is de conventie uit hoofdstuk 5. Dit is de eigen administratie van de bibliotheek, geen onderdeel van haar beloften. In les 6 maak je die voorzichtig toegankelijk, via methoden die kopieën teruggeven.
Waarom bewaart Member geen verwijzing naar de Library waarbij het hoort?
Wat je nu bouwt
Geef Library in __init__ haar drie collecties. Maak van register_member en add_item echte methoden die er iets aan toevoegen en een herhaalde id afwijzen.
De andere vijf blijven zoals ze zijn. Je bouwt een slotproject in een werkbare volgorde. Er is niets mis met een klasse die voor de helft tijdelijk is ingevuld, zolang je kunt aangeven welke helft.
Opdracht
Geef Library in library.py haar collecties en haar twee bewakers.
__init__ krijgt alleen self en maakt drie lege dictionaries met id’s als sleutels: één voor items, één voor leden en één voor actieve uitleningen. Geef ze namen met een underscore vooraan; ze vormen de eigen administratie van de bibliotheek.
add_item(item) slaat het item op onder zijn item_id en werpt LibraryError op wanneer die id al in de catalogus staat.
register_member(member) doet hetzelfde voor member.member_id.
Importeer LibraryError uit errors. Geef elke weigering een melding die de dubbele id noemt.
Laat de andere vijf methoden NotImplementedError opwerpen.