0%

Final Capstone: A Community Lending Library · practice

Draw the First Object Collaboration

Before writing the interesting , draw the picture. Not on paper necessarily, but somewhere, because the shape you choose now is the thing every later lesson has to live inside.

One diagram, four boxes

        Library
        |  owns the catalog     -> Book, Game
        |  owns the members     -> Member
        |  owns the active loans -> Loan

Every arrow points the same way, and that is the design. Library knows about items, members, and loans. None of them knows about Library.

Chapter 6 gave the reason: a required dependency back the other way makes the harder to build and test separately. If Member required a library in its constructor, a test of member validation would have to supply one too. This project has no member operation that needs that dependency.

The Library owns item, member, and loan collections, with one-way dependencies to the domain records.

The Chapter 10 word for this shape is a dependency direction, and keeping it one-way is most of what makes a small system stay workable.

Owning a collection means owning the rules about it

Library holds three collections. Holding them is not the interesting part; deciding what may go into them is.

Two rules apply from the start, and both come out of the contract:

duplicate item and member identifiers are rejected

An id has to mean exactly one thing, or checkout("b1", ...) is ambiguous and the whole contract falls apart. So add_item and register_member are not blind appends. They are the gatekeepers for their collections.

That makes them the first real methods worth writing, and the first place the seeded errors get used:

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")
    ...

Chapter 12’s applies unchanged: refusing is part of what the method promises, not an accident.

Why a dictionary keyed by id

The contract hands you identifiers. checkout(item_id, member_id, checkout_day) gets a and has to find the item.

A would mean a search every time, and worse, it would make “is this id already taken?” a scan. A keyed by the id says exactly what you mean: one item per id, and the duplicate check is the same lookup as the retrieval.

self._items = {}
self._members = {}
self._loans = {}

The leading underscore is Chapter 5’s convention. These are the library’s own bookkeeping, not part of what it promises. Lesson 6 is where they get exposed carefully, through methods that hand out copies.

Why does Member not hold a reference to the Library it belongs to?

What to build now

Give Library its three collections in __init__, and turn register_member and add_item into real methods that add to them and reject a repeated id.

The other five stay as they are. A capstone gets built in working order, and there is nothing wrong with a class that is half placeholder as long as you can say which half.

Task

Give Library its collections and its two gatekeepers, in library.py.

__init__ takes nothing but self and sets up three empty , keyed by id: one for items, one for members, one for active loans. Give them underscore names; they are the library’s own bookkeeping.

add_item(item) stores the item under its item_id, and raises LibraryError when that id is already in the catalog.

register_member(member) does the same for member.member_id.

Import LibraryError from errors. Give each refusal a message naming the id that clashed.

Leave the other five raising NotImplementedError.