Unit 4 Project: Build a Small Object Model

Put classes, methods, and state together in one mini-system

Intermediate 14 min

In this lesson

In this project you’ll build a small object model in Python — a set of cooperating objects — applying everything from Unit 4. You’ll design it, then build it up one class and one method at a time. The goal isn’t a huge program; it’s seeing classes, methods, and state work together in something that actually runs.

Explain it like I’m 5

A small object model is a tiny pretend world where each thing knows its own data and its own actions — and they work together to get something done.

Design before you code

Start by naming the things in your domain and what each one knows and does. For a library: a Book knows its title and whether it’s borrowed; a Library holds many books and can add, borrow, and list them. Sketching this first keeps each class small and focused — a classic OOP habit.

Build it one method at a time

Add classes and methods incrementally, checking each as you go rather than writing everything and hoping. Notice how the Library composes Book objects — it holds them in a list rather than inheriting from them, because a library has books, it isn’t a kind of book.

Example
class Book:
    def __init__(self, title):
        self.title = title
        self.borrowed = False

class Library:
    def __init__(self):
        self.books = []

    def add(self, title):
        self.books.append(Book(title))

    def borrow(self, title):
        for book in self.books:
            if book.title == title and not book.borrowed:
                book.borrowed = True
                return True
        return False

    def available(self):
        return [book.title for book in self.books if not book.borrowed]

lib = Library()
lib.add("Python 101")
lib.add("Clean Code")
lib.borrow("Python 101")
print(lib.available())
Output
['Clean Code']
The Library composes Book objects and manages their state.

Test by hand, then refactor

Drive your objects through a few realistic steps and check the results — borrow a book twice and confirm the second attempt fails, list what’s left, and so on. This is exactly the manual testing habit from debugging basics. If you find yourself copying the same dictionary-shuffling code in several places, that repetition is the signal to fold it into a method on a class.

Common mistake: Building many classes before the first one works

Why it happens:

It’s tempting to design the whole system up front and write every class at once.

How to fix it:

Get one class running end to end, then add the next. Each working piece tells you whether the design holds up.

Common mistake: Mixing input() prompts into every method

Why it happens:

Asking the user right where you need the value feels direct.

How to fix it:

Pass values in as arguments and return results; do the input() and print() in one outer layer. Your objects stay testable and reusable.

Common mistake: Using inheritance when two objects share only a tiny detail

Why it happens:

Any shared field can make inheritance look appropriate.

How to fix it:

Prefer composition — one object holding another — unless the child genuinely is a kind of the parent. A Library has Books; it isn’t a kind of book.

In a library system, which parts make good classes?

Where should user input live in an object-oriented program?

Why does Library hold Book objects rather than inherit from Book?

Mini exercise (hard)

Build a Library that holds Book objects. Give Library an add(title) method, a borrow(title) method that marks a matching available book as borrowed (and returns True, or False if it can’t), and an available() method that returns the titles still on the shelf. Add two books, borrow one, and print what’s available.

Do it here. Finish the code below and press Run to try your answer right in the browser — no setup needed.

class Book:
    def __init__(self, title):
        self.title = title
        self.borrowed = False

class Library:
    def __init__(self):
        self.books = []

    def add(self, title):
        # TODO: add a new Book to self.books
        pass

    def borrow(self, title):
        # TODO: mark the first matching available book borrowed, return True; else False
        return False

    def available(self):
        # TODO: return a list of titles that are not borrowed
        return []

lib = Library()
lib.add("Python 101")
lib.add("Clean Code")
lib.borrow("Python 101")
print(lib.available())

What to learn next

You pulled the whole unit together: you designed a small domain as cooperating classes, built it one method at a time, tested object behaviour by hand, and saw why composition (a library that has books) often beats inheritance. You built a working Library of Book objects that can add, borrow, and list what’s available.

You now have the beginner foundations and object-oriented thinking. The Intermediate path continues by turning Python into a tool for real work. The Automation Mindset opens Unit 5: how to spot what’s worth automating and how to do it safely with a dry-run first. When you’re ready, continue to the next lesson.