Goal: move shared state up into LibraryItem, make Book and DVD extend it, add Student and Staff, and watch one loop produce different answers.
Warning Book loses its title and isBorrowed: they now live in the parent. If your old Book tests stop compiling, that is expected. Update them.

1. The superclass LibraryItem
Everything every item has: id, title, isBorrowed. It also provides a default late fee which subclasses may override.
src/lms/model/LibraryItem.javaThe superclass
package lms.model; /** * LibraryItem — introduced this week as the shared parent of Book and DVD. * * Encapsulation (from Week 2) is still here — private fields, validated * setters — but the new idea this week is INHERITANCE: Book and DVD will * extend this class instead of repeating id/title/isBorrowed themselves. */ public abstract class LibraryItem { 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; } public void borrowItem() { this.isBorrowed = true; } public void returnItem() { this.isBorrowed = false; } /** Every subclass may override this — that's what makes Part 2 (Polymorphism) below work. */ public double calculateLateFee(int daysLate) { return daysLate * 10.0; } @Override public String toString() { return getClass().getSimpleName() + "{id='" + id + "', title='" + title + "', borrowed=" + isBorrowed + "}"; } }
2. Subclasses with extends and super
The first line of each subclass constructor is super(id, title). DVD overrides calculateLateFee: always write @Override.
src/lms/model/Book.javaBook extends LibraryItem
package lms.model; public class Book extends LibraryItem { private String author; 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; } }
src/lms/model/DVD.javaDVD overrides the fee
package lms.model; public class DVD extends LibraryItem { private int durationMinutes; public DVD(String id, String title, int durationMinutes) { super(id, title); this.durationMinutes = durationMinutes; } public int getDurationMinutes() { return durationMinutes; } @Override public double calculateLateFee(int daysLate) { return daysLate * 25.0; // DVDs cost more per day late } }
3. Members: Member, Student, Staff
Student and Staff differ only by borrowing limit, passed up through super.
src/lms/model/Member.java
package lms.model; /** Member — now the parent of Student and Staff, introduced this week. */ public class 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); } @Override public String toString() { return getClass().getSimpleName() + "{id='" + memberId + "', name='" + name + "', borrowedCount=" + borrowedCount + ", limit=" + maxBorrowLimit + "}"; } }
src/lms/model/Student.java
package lms.model; public class Student extends Member { private static final int STUDENT_BORROW_LIMIT = 3; public Student(String memberId, String name) { super(memberId, name, STUDENT_BORROW_LIMIT); } }
4. Library: polymorphism, overloading and instanceof
Three ideas live here. Read the comments in the file:
printAllLateFeeshas noif, yet each item charges its own fee: dynamic binding.- The three
addItemoverloads are chosen at compile time by the variable's declared type: static binding. streamIfPossibleusesinstanceoffor a feature only DVDs have.
src/lms/service/Library.java
package lms.service; import lms.model.Book; import lms.model.DVD; import lms.model.LibraryItem; import lms.model.Member; import java.util.ArrayList; import java.util.List; /** * Library — Week 3 version (Inheritance + Polymorphism combined). * * Two ideas land here in the same lab: * Part 1 (Inheritance): the fields below are List<LibraryItem> and * List<Member> — the PARENT types from * lms.model — not List<Book> or List<Student>. * Part 2 (Polymorphism): printAllLateFees(), the addItem() overloads, * and streamIfPossible() below show what that * buys you. */ public class Library { private final List<LibraryItem> items = new ArrayList<>(); private final List<Member> members = new ArrayList<>(); // ---- Dynamic binding: one method, one loop, many behaviours ---- /** * Prints every item's late fee. This method never mentions Book or * DVD by name — it only knows about LibraryItem. At runtime, Java * still calls the correct calculateLateFee() for each real object. * That's dynamic (runtime) binding — and it only works because of * the inheritance hierarchy you just built in Part 1. */ 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)); } } // ---- Static binding: overloading resolved at compile time ---- /** Generic entry point — works for any LibraryItem, including future subclasses. */ public void addItem(LibraryItem item) { items.add(item); System.out.println("Added via addItem(LibraryItem): " + item.getTitle()); } /** * Overload #1. The COMPILER decides which addItem() to call based on * the declared type of the argument at the call site — that decision * is made before the program ever runs. This is static binding. */ public void addItem(Book book) { items.add(book); System.out.println("Added via addItem(Book) - extra step: logging to 'new arrivals': " + book.getTitle()); } /** Overload #2 — DVDs get their own extra step instead. */ public void addItem(DVD dvd) { items.add(dvd); System.out.println("Added via addItem(DVD) - extra step: checking disc condition: " + dvd.getTitle()); } public void addMember(Member member) { members.add(member); } // ---- instanceof: only when you genuinely need the specific type ---- /** * Streaming only makes sense for a DVD, not for every LibraryItem — * so this is exactly the situation where a safe instanceof check * (with pattern matching) is the right tool, not a design flaw. */ public void streamIfPossible(LibraryItem item) { if (item instanceof DVD dvd) { System.out.println("Streaming \"" + dvd.getTitle() + "\" (" + dvd.getDurationMinutes() + " min)..."); } else { System.out.println(item.getTitle() + " cannot be streamed - it isn't a DVD."); } } 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 3 demo: Inheritance + Polymorphism, combined. * * Try this yourself: * 1. Add a third item type of your own (e.g. Magazine extends LibraryItem) * and confirm printAllLateFees() handles it with ZERO changes to * that method — that's the payoff of Part 1's inheritance work. * 2. Comment out the addItem(Book) overload and re-run — notice * addItem(b1) now falls back to addItem(LibraryItem), and the * "new arrivals" logging step silently disappears. That's static * binding: the compiler picked a different method entirely. * 3. Call streamIfPossible() with a Book instead of a DVD. */ public class Main { public static void main(String[] args) { Library library = new Library(); Book b1 = new Book("B001", "Clean Code", "Robert C. Martin"); Book b2 = new Book("B002", "Effective Java", "Joshua Bloch"); DVD d1 = new DVD("D001", "Introduction to Algorithms", 120); // Static binding: the compiler picks the overload matching each // variable's DECLARED type (Book, Book, DVD) at compile time. library.addItem(b1); library.addItem(b2); library.addItem(d1); library.addMember(new Student("M001", "Nadeesha Perera")); library.addMember(new Staff("M002", "Mr. Kasun Silva")); library.listItems(); library.listMembers(); // Dynamic binding: same loop, different calculateLateFee() per object. library.printAllLateFees(3); // instanceof: only DVDs can be streamed. library.streamIfPossible(d1); library.streamIfPossible(b1); } }
Experiment: the same object, two overloads
Book b = new Book("B9", "Test", "Someone");
LibraryItem same = b;
library.addItem(b); // prints: addItem(Book)
library.addItem(same); // prints: addItem(LibraryItem) <- same object!
Check Why? Overloading looks only at the declared type. Overriding looks at the real object. Memorise that sentence.
Checkpoint: is it done?
BookandDVDdo not declareid,titleorisBorrowed- Every subclass constructor starts with
super(...) printAllLateFeesprints 30.0 and 75.0 without anyif- UML saved as
docs/uml-week3.png; PR: Week 3: Inheritance & Polymorphism