Step 4: Enforce the rules with exceptions
Now the heart of the system: LibraryService. borrow refuses when the book is already on loan or when the member has hit the limit; giveBack refuses when the member does not hold that book. Rule violations are reported with a custom LibraryException, which the caller catches and shows to the user. Watch the sequence of requests below and which ones fail.
ExampleJava
import java.util.*;
public class Main {
static class Book {
final String isbn, title;
boolean available = true;
Book(String isbn, String title) { this.isbn = isbn; this.title = title; }
}
static class Member {
final String name;
final List<Book> loans = new ArrayList<>();
Member(String name) { this.name = name; }
}
static class LibraryException extends RuntimeException {
LibraryException(String message) { super(message); }
}
static class LibraryService {
static final int MAX_LOANS = 2;
void borrow(Member member, Book book) {
if (!book.available) throw new LibraryException(book.title + " is already on loan");
if (member.loans.size() >= MAX_LOANS) throw new LibraryException(member.name + " has reached the limit of " + MAX_LOANS + " loans");
book.available = false;
member.loans.add(book);
}
void giveBack(Member member, Book book) {
if (!member.loans.remove(book)) throw new LibraryException(member.name + " does not have " + book.title);
book.available = true;
}
}
public static void main(String[] args) {
LibraryService library = new LibraryService();
Member ada = new Member("Ada");
Member sam = new Member("Sam");
Book a = new Book("1", "Clean Code");
Book b = new Book("2", "Refactoring");
Book c = new Book("3", "Design Patterns");
String[] steps = { "ada borrows a", "sam borrows a", "ada borrows b", "ada borrows c", "ada returns a", "sam returns b", "sam borrows a" };
for (String step : steps) {
try {
switch (step) {
case "ada borrows a" -> library.borrow(ada, a);
case "sam borrows a" -> library.borrow(sam, a);
case "ada borrows b" -> library.borrow(ada, b);
case "ada borrows c" -> library.borrow(ada, c);
case "ada returns a" -> library.giveBack(ada, a);
case "sam returns b" -> library.giveBack(sam, b);
}
System.out.println("OK " + step);
} catch (LibraryException e) {
System.out.println("ERROR " + step + ": " + e.getMessage());
}
}
System.out.println("Ada has " + ada.loans.size() + " loans, Sam has " + sam.loans.size());
}
}Output
OK ada borrows a ERROR sam borrows a: Clean Code is already on loan OK ada borrows b ERROR ada borrows c: Ada has reached the limit of 2 loans OK ada returns a ERROR sam returns b: Sam does not have Refactoring OK sam borrows a Ada has 1 loans, Sam has 1
Each failure has a clear message, and the state stays consistent: a refused borrow changes nothing. Notice that Sam borrowing book a finally works after Ada returned it.
Quick check
What happens to the data when borrow throws a LibraryException?
Finish the quick check (and any code exercise) to complete this lesson, or mark it done yourself.
Tip: use the left and right arrow keys to move between lessons.