Goal: make LibraryItem abstract, introduce the interfaces Loanable and Reservable, add Magazine, and make Member sortable.

1. Two contracts
src/lms/model/Loanable.java
package lms.model; /** * Loanable - a contract: "anything that can be loaned to a Member". * * An interface says WHAT must be possible, never HOW. Any class that * implements Loanable promises to provide both methods below. */ public interface Loanable { void loan(Member member); void returnItem(); }
src/lms/model/Reservable.java
package lms.model; /** * Reservable - a second, separate contract. * * Only some items can be reserved (Books, but not DVDs or Magazines in * our library), so this capability does NOT belong on LibraryItem. * An interface lets us attach it to exactly the classes that need it. */ public interface Reservable { void reserve(Member member); void cancelReservation(); boolean isReserved(); }
2. The abstract class
calculateLateFee now has no body. LibraryItem also implements Loanable, so the loan logic lives here once.
src/lms/model/LibraryItem.java
package lms.model; /** * LibraryItem - Week 4 version. * * Two changes from Week 3: * 1. It now IMPLEMENTS the Loanable interface (a contract it fulfils). * 2. calculateLateFee() is now truly ABSTRACT - no default body. * Every subclass must write its own, or it will not compile. */ public abstract class LibraryItem implements Loanable { private String id; private String title; private boolean isBorrowed; public LibraryItem(String id, String title) { setId(id); setTitle(title); this.isBorrowed = false; } public String getId() { return id; } public String getTitle() { return title; } public boolean isBorrowed() { return isBorrowed; } public void setId(String id) { if (id == null || id.isBlank()) throw new IllegalArgumentException("Id cannot be blank"); this.id = id; } public void setTitle(String title) { if (title == null || title.isBlank()) throw new IllegalArgumentException("Title cannot be blank"); this.title = title; } // ---- Loanable contract (shared implementation lives here) ---- @Override public void loan(Member member) { if (isBorrowed) { System.out.println(title + " is already on loan."); } else if (member.getBorrowedCount() >= member.getMaxBorrowLimit()) { System.out.println(member.getName() + " has reached the limit of " + member.getMaxBorrowLimit() + " items."); } else { isBorrowed = true; member.incrementBorrowedCount(); System.out.println(title + " loaned to " + member.getName()); } } @Override public void returnItem() { isBorrowed = false; System.out.println(title + " returned."); } /** No body: each subclass MUST define its own late-fee rule. */ public abstract double calculateLateFee(int daysLate); @Override public String toString() { return getClass().getSimpleName() + "{id='" + id + "', title='" + title + "', borrowed=" + isBorrowed + "}"; } }
3. Three concrete items
Book is also Reservable; DVD is not; Magazine is new, and the compiler forces its fee rule.
src/lms/model/Book.javaextends LibraryItem implements Reservable
package lms.model; /** Book - a LibraryItem that can ALSO be reserved (implements Reservable). */ public class Book extends LibraryItem implements Reservable { private String author; private Member reservedBy; public Book(String id, String title, String author) { super(id, title); setAuthor(author); } public String getAuthor() { return author; } public void setAuthor(String author) { if (author == null || author.isBlank()) throw new IllegalArgumentException("Author cannot be blank"); this.author = author; } /** Books used to inherit a default rate; now each class states its own. */ @Override public double calculateLateFee(int daysLate) { return daysLate * 10.0; } // ---- Reservable contract ---- @Override public void reserve(Member member) { if (reservedBy != null) { System.out.println(getTitle() + " is already reserved by " + reservedBy.getName()); } else { reservedBy = member; System.out.println(getTitle() + " reserved for " + member.getName()); } } @Override public void cancelReservation() { reservedBy = null; System.out.println("Reservation cancelled for " + getTitle()); } @Override public boolean isReserved() { return reservedBy != null; } }
src/lms/model/Magazine.java
package lms.model; /** * Magazine - a NEW kind of item added this week. * * Try this experiment: delete the calculateLateFee() method below and * try to compile. Because LibraryItem declares it abstract, the compiler * refuses: "Magazine is not abstract and does not override abstract method". */ public class Magazine extends LibraryItem { private int issueNumber; public Magazine(String id, String title, int issueNumber) { super(id, title); this.issueNumber = issueNumber; } public int getIssueNumber() { return issueNumber; } @Override public double calculateLateFee(int daysLate) { return daysLate * 5.0; } }
4. Sorting members with Comparable
src/lms/model/Member.javacompareTo: most active first
package lms.model; /** * Member - parent of Student and Staff. Week 4: implements Comparable so a * list of members can be sorted with Collections.sort(). */ public class Member implements Comparable<Member> { private String memberId; private String name; private int borrowedCount; private int maxBorrowLimit; public Member(String memberId, String name, int maxBorrowLimit) { setMemberId(memberId); setName(name); this.maxBorrowLimit = maxBorrowLimit; this.borrowedCount = 0; } public String getMemberId() { return memberId; } public String getName() { return name; } public int getBorrowedCount() { return borrowedCount; } public int getMaxBorrowLimit() { return maxBorrowLimit; } public void setMemberId(String memberId) { if (memberId == null || memberId.isBlank()) throw new IllegalArgumentException("Member ID cannot be blank"); this.memberId = memberId; } public void setName(String name) { if (name == null || name.isBlank()) throw new IllegalArgumentException("Name cannot be blank"); this.name = name; } public void setBorrowedCount(int borrowedCount) { if (borrowedCount < 0) throw new IllegalArgumentException("Borrowed count cannot be negative"); this.borrowedCount = borrowedCount; } public void incrementBorrowedCount() { setBorrowedCount(this.borrowedCount + 1); } /** * Natural order: MOST active member first (highest borrowedCount). * Collections.sort(list) calls this method to decide which of two * members comes first. */ @Override public int compareTo(Member other) { return Integer.compare(other.borrowedCount, this.borrowedCount); } @Override public String toString() { return getClass().getSimpleName() + "{id='" + memberId + "', name='" + name + "', borrowedCount=" + borrowedCount + ", limit=" + maxBorrowLimit + "}"; } }
5. Library talks to interfaces
src/lms/service/Library.java
package lms.service; import lms.model.Book; import lms.model.DVD; import lms.model.LibraryItem; import lms.model.Loanable; import lms.model.Member; import lms.model.Reservable; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * Library - Week 4 version (Abstract Classes & Interfaces). * * Kept from Week 3: printAllLateFees(), the addItem() overloads. * New this week: methods that talk to INTERFACE types (Loanable, * Reservable) instead of concrete classes, and a Comparable-based sort. */ public class Library { private final List<LibraryItem> items = new ArrayList<>(); private final List<Member> members = new ArrayList<>(); // ---- from Week 3 (unchanged) ---- public void addItem(LibraryItem item) { items.add(item); System.out.println("Added via addItem(LibraryItem): " + item.getTitle()); } public void addItem(Book book) { items.add(book); System.out.println("Added via addItem(Book): " + book.getTitle()); } public void addItem(DVD dvd) { items.add(dvd); System.out.println("Added via addItem(DVD): " + dvd.getTitle()); } public void addMember(Member member) { members.add(member); } public void printAllLateFees(int daysLate) { System.out.println("--- Late fees if " + daysLate + " day(s) late ---"); for (LibraryItem item : items) { System.out.println(item.getTitle() + ": " + item.calculateLateFee(daysLate)); } } // ---- NEW in Week 4 ---- /** * The parameter type is the INTERFACE Loanable, not LibraryItem. * This method would work for anything that promises to be loanable, * even a class that is not a LibraryItem at all. */ public void lend(Loanable thing, Member member) { thing.loan(member); } /** * Only some items are Reservable. Checking for the INTERFACE (not for * Book) means a future Reservable class works here with no changes. */ public void reserveIfPossible(LibraryItem item, Member member) { if (item instanceof Reservable r) { r.reserve(member); } else { System.out.println(item.getTitle() + " cannot be reserved."); } } /** Prints members ranked by activity, using Member's compareTo(). */ public void printMembersByActivity() { List<Member> sorted = new ArrayList<>(members); Collections.sort(sorted); System.out.println("--- Members ranked by activity ---"); for (Member m : sorted) { System.out.println(m.getName() + " (" + m.getBorrowedCount() + " borrowed)"); } } public void listItems() { System.out.println("--- Items in the library ---"); for (LibraryItem item : items) System.out.println(item); } public void listMembers() { System.out.println("--- Registered members ---"); for (Member m : members) System.out.println(m); } public List<LibraryItem> getItems() { return items; } public List<Member> getMembers() { return members; } }
src/lms/Main.java
package lms; import lms.model.*; import lms.service.Library; /** * Main - Week 4 demo: Abstract Classes & Interfaces. * * Try this yourself: * 1. Delete calculateLateFee() from Magazine and compile. Read the error. * 2. Add "implements Reservable" to DVD and satisfy the compiler. * 3. Write a new class Poster that implements Loanable but is NOT a * LibraryItem, then pass it to library.lend(...). * 4. Change compareTo() so the LEAST active member sorts first. */ public class Main { public static void main(String[] args) { Library library = new Library(); Book book = new Book("B001", "Clean Code", "Robert C. Martin"); DVD dvd = new DVD("D001", "Introduction to Algorithms", 120); Magazine mag = new Magazine("G001", "Software Weekly", 42); library.addItem(book); library.addItem(dvd); library.addItem(mag); Student nadeesha = new Student("M001", "Nadeesha Perera"); Staff kasun = new Staff("M002", "Mr. Kasun Silva"); library.addMember(nadeesha); library.addMember(kasun); // Abstract method: three classes, three different fee rules library.printAllLateFees(3); // Loanable interface System.out.println("--- Loans ---"); library.lend(book, nadeesha); library.lend(book, kasun); // already on loan library.lend(dvd, kasun); library.lend(mag, kasun); // Reservable interface: only Book qualifies System.out.println("--- Reservations ---"); library.reserveIfPossible(book, kasun); library.reserveIfPossible(dvd, nadeesha); // Comparable library.printMembersByActivity(); } }
mkdir -p out
javac -d out $(find src -name "*.java")
java -cp out lms.Main
Added via addItem(Book): Clean Code
Added via addItem(DVD): Introduction to Algorithms
Added via addItem(LibraryItem): Software Weekly
--- Late fees if 3 day(s) late ---
Clean Code: 30.0
Introduction to Algorithms: 75.0
Software Weekly: 15.0
--- Loans ---
Clean Code loaned to Nadeesha Perera
Clean Code is already on loan.
Introduction to Algorithms loaned to Mr. Kasun Silva
Software Weekly loaned to Mr. Kasun Silva
--- Reservations ---
Clean Code reserved for Mr. Kasun Silva
Introduction to Algorithms cannot be reserved.
--- Members ranked by activity ---
Mr. Kasun Silva (2 borrowed)
Nadeesha Perera (1 borrowed)
Capture these compiler errors for your PR
| You do this | The compiler says |
|---|---|
new LibraryItem("X","Y") |
LibraryItem is abstract; cannot be instantiated |
delete calculateLateFee from Magazine |
Magazine is not abstract and does not override abstract method calculateLateFee(int) in LibraryItem |
add implements Reservable to DVD |
DVD is not abstract and does not override abstract method isReserved() in Reservable |
item.getAuthor() on a LibraryItem variable |
cannot find symbol |
Tip These errors are your friends: the compiler is enforcing the contract you designed.
Checkpoint: is it done?
new LibraryItem(...)fails to compile and you saved the messageDVDis notReservable, yetreserveIfPossible(dvd, …)works- Members are ranked most-active first
- UML saved as
docs/uml-week4.png; PR: Week 4: Abstract Classes & Interfaces