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.

Target design for Step 2: two hierarchies, one Library
Target design for Step 2: two hierarchies, one Library

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:

  1. printAllLateFees has no if, yet each item charges its own fee: dynamic binding.
  2. The three addItem overloads are chosen at compile time by the variable's declared type: static binding.
  3. streamIfPossible uses instanceof for 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);
    }
}
mkdir -p out javac -d out $(find src -name "*.java") java -cp out lms.Main
Added via addItem(Book) - extra step: logging to 'new arrivals': Clean Code Added via addItem(Book) - extra step: logging to 'new arrivals': Effective Java Added via addItem(DVD) - extra step: checking disc condition: Introduction to Algorithms --- Items in the library --- Book{id='B001', title='Clean Code', borrowed=false} Book{id='B002', title='Effective Java', borrowed=false} DVD{id='D001', title='Introduction to Algorithms', borrowed=false} --- Registered members --- Student{id='M001', name='Nadeesha Perera', borrowedCount=0, limit=3} Staff{id='M002', name='Mr. Kasun Silva', borrowedCount=0, limit=10} --- Late fees if 3 day(s) late --- Clean Code: 30.0 Effective Java: 30.0 Introduction to Algorithms: 75.0 Streaming "Introduction to Algorithms" (120 min)... Clean Code cannot be streamed - it isn't a DVD.

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?

  • Book and DVD do not declare id, title or isBorrowed
  • Every subclass constructor starts with super(...)
  • printAllLateFees prints 30.0 and 75.0 without any if
  • UML saved as docs/uml-week3.png; PR: Week 3: Inheritance & Polymorphism