WeCodeAcademy

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.