Dataslope logoDataslope

Capstone: A Library System

An end-to-end modeling exercise that uses encapsulation, composition, interfaces, polymorphism, and responsibility-driven design

This capstone applies every idea from the course to one system: a small library management system. Read the description, work through the modeling steps with us, then write the missing pieces.

The story

A library has a catalog of books. People can become members of the library. Members may borrow books for a limited time. The library tracks every loan and can list which books are currently out and to whom. Different libraries enforce different borrowing policies (e.g. how many books one person may hold at once).

That paragraph contains everything we need.

Step 1 — find the objects

Underline the nouns. The candidates are:

  • Library — coordinates everything
  • Book — a thing on a shelf
  • Member — a person registered with the library
  • Loana fact: this book is out to this member, until this date
  • BorrowingPolicya rule: who may borrow how much

Loans and policies are subtle and easy to miss. They're not nouns in the strictest sense, but they're first-class concepts in the domain, so they deserve their own classes. (If you reduced them to fields on Book or methods on Library, the design would get muddier.)

Step 2 — assign responsibilities

Using the cards format from Responsibility-Driven Design:

ClassResponsibilitiesCollaborators
BookKnow its title, author, and ISBN
MemberKnow its name and id; know its current loans; count themLoan
LoanKnow the borrowed book, the member, and the due date; know if overdueBook, Member
BorrowingPolicyDecide if a given member may borrow another bookMember
LibraryHold the catalog and members; create and track loans; enforce the policyall of the above

Step 3 — sketch the relationships

Two interesting design choices in that diagram:

  1. BorrowingPolicy is an interface. Different libraries can plug in different rules without touching Library. This is programming to an interface (and the Strategy pattern again).
  2. Loan is a first-class object that aggregates a book and a member. The Member also keeps a list of its own loans — bidirectional access — but Loan itself owns the due date and the "is overdue?" decision.

Step 4 — message flow for borrowing

The conversation that happens when a member asks to borrow a book:

Library does not implement the policy itself. It asks. The policy in turn asks the member how many loans they have. Nobody reaches into anyone's fields.

Step 5 — build it

Most of the code is provided. You will finish two pieces:

  1. Loan.isOverdue(int nowDay) — return whether the current day is strictly after the loan's due day.
  2. LimitPolicy.mayBorrow(Member m) — return whether the member's current loan count is strictly less than the limit.

Read Library.borrow(...) carefully — it's the orchestration in miniature.

Code Block
Java 8 (Update 492)

Step 6 — read your own design

If you completed the exercise, take a moment to look at what you just built. Notice that:

  • Every class has a small, focused responsibility. None of them exceeded 30 or 40 lines.
  • Library is the orchestrator. It owns very little business logic; it sends messages.
  • BorrowingPolicy is a plug-in. Replacing LimitPolicy(2) in Main with new OpenPolicy() changes the library's behavior without changing a single line of Library.
  • Loan is a first-class object. The "is overdue?" decision lives where the data lives.
  • Nothing reaches into anyone else's fields. Every interaction is a message.

That is what a pure-OOP model looks like. The exact same shape scales to a real library system with thousands of books and hundreds of members, simply by changing the storage of books, members, and active from in-memory lists to a database — and because that storage is hidden behind Library's methods, no other class would notice.

Stretch ideas (optional)

If you want to keep going, try any of these:

  • Add a Reservation class: a member can reserve a book that's currently checked out and be next in line when it returns.
  • Add a WeekendPolicy that doubles the allowed loan count on weekends, demonstrating polymorphic policy composition.
  • Replace the int today/loanDays with a java.time.LocalDate and pass an injected Clock into Library. This is a real-world pattern for testability.
  • Introduce a Catalog class that owns the book list and an Audit class that records every borrow and return — splitting Library's remaining work along its natural seams.

Each of those is a textbook responsibility-driven extension.

Test your understanding

QuestionSelect one

Why is BorrowingPolicy declared as an interface in this design?

Because Java requires every Library to use one

So that different libraries can plug in different policies without modifying Library

Because Java forbids classes from holding boolean methods

To save memory

QuestionSelect one

Why does the Loan class own the isOverdue(...) method instead of Library owning it?

Library is too small to hold any methods

Java forbids Library from having boolean methods

The relevant data (the due date) lives on Loan, so the behavior belongs there too — that's "tell, don't ask"

To make Loan extend Library

QuestionSelect one

If we wanted to add a "weekend libraries allow more loans" rule, the cleanest change in this design is:

Add a giant if in Library.borrow that checks the day of the week

Add a new field to Member

Write a new class WeekendPolicy implements BorrowingPolicy and pass it into Library's constructor — Library itself does not change

Modify LimitPolicy to know about weekends

On this page