Final Capstone: A Community Lending Library · practice
Support Polymorphic Behavior
Lesson 5 left something wrong on purpose:
LOAN_PERIOD_DAYS = 14
Every item gets a fortnight. A novel and a board game are not the same kind of thing to lend, and any real library knows it. Now you fix it, and the way you fix it is the point of this lesson.
The version to reject
Here is the obvious repair, and it is worth writing out so you can see what is wrong with it:
if isinstance(item, Book):
period = 21
elif isinstance(item, Game):
period = 7
It works. It also puts a fact about books inside Library, and it means adding a third kind of item is an edit to checkout rather than a new class.
Chapter 9 named the smell: a chain of type tests is usually an Game, because that is a fact about games.
Ask the item
class Book:
def loan_period_days(self):
return 21
class Game:
def loan_period_days(self):
return 7
and checkout stops deciding:
item = self._items[item_id]
due_day = checkout_day + item.loan_period_days()
One line, and every type test is gone. Library no longer knows there is such a thing as a book. It knows it holds items, and that an item can say how long it may be kept.
Add a third kind of item next year and checkout does not change. That is the property worth having, and it is the one the isinstance version cannot give you.
No base class required
Chapter 9 was careful about this, so this lesson will be too.
Book and Game share no parent. They are not related by inheritance at all. What makes them interchangeable here is that both answer loan_period_days() and describe(), and Library never asks for anything else.
That is duck typing, and it is enough. A shared base class would buy something only if there were real behavior to share, and right now there is none: the two implementations have nothing in common but their name.
The contract agrees, and says so:
Inheritance is optional.
BookandGamemay share a small base class or simply satisfy the same public operations through duck typing.
Prefer the version with less machinery until the machinery earns its place.
Book and Game share no base class, yet Library treats them the same. What makes that work?
The second polymorphic method
describe() is the other operation the contract asks every item to support, and it is where the difference between the two
Solaris by Lem
Carcassonne (up to 5 players)
Same call, different sentence, and no caller has to know which kind of item it is holding. That is what makes a report over a mixed catalog possible to write at all.
What to build now
Lesson 2 already gave Book and Game both operations. This lesson is about the other side of that arrangement: making Library actually use them.
You will add describe_catalog() to your saved Library class. It reports the available items in catalog order by asking each item for its description. The fallback starter shows an isinstance version for comparison, but opening this lesson preserves your saved file; it does not insert this new checkout also needs to change: it currently applies one number to everything.
Both become a single call to the item. When you are finished, library.py will no longer import or refer to the Book or Game classes, and that is the thing to check.
Task
Take the type tests out of library.py.
Add describe_catalog(self) to your existing Library class. It returns descriptions of the items from available_items(), in that same order, by calling each item’s describe(). If you are using the fallback starter, replace its type-checking version. Opening this lesson does not overwrite your saved file or add a
Then do the same to checkout. It currently gives everything LOAN_PERIOD_DAYS. Have it work the due day out by asking the item for its own loan period, and delete the constant.
When you are done, library.py should not import or refer to Book or Game in executable code, or inspect item