Goal: create the first three LMS classes with hidden data and validation, and run them from a Main.

Tip Open this page next to your IDE. Type the code yourself. Copy-paste teaches your fingers, not your brain. The full reference files are collapsed under each heading; open them to compare after you tried.

What you will have at the end: a console program that registers books and members, lists them, and rejects bad input.

Target design for Step 1: Library has books and members
Target design for Step 1: Library has books and members

1. Create the project

Make a folder lms-<index> (your cloned repository) and this structure. Package names must match folder names.

lms-<index>/
├── .gitignore
├── README.md
├── docs/
└── src/
    └── lms/
        ├── Main.java
        ├── model/
        │   ├── Book.java
        │   └── Member.java
        └── service/
            └── Library.java

Start your Git branch first: git checkout -b week2-encapsulation.

2. Write Book

Four private fields, a constructor that calls the setters, getters, and setters that reject blank text. Notice the constructor never assigns this.title = title directly. That would let a blank title slip in.

Tip Commit as soon as Book compiles: "Add Book with validated setters".

src/lms/model/Book.javaFull reference file
package lms.model;

public class Book {
    private String title;
    private String author;
    private String isbn;
    private boolean isBorrowed;

    public Book(String title, String author, String isbn) {
        setTitle(title);
        setAuthor(author);
        setIsbn(isbn);
        this.isBorrowed = false;
    }

    public String getTitle() { return title; }
    public String getAuthor() { return author; }
    public String getIsbn() { return isbn; }
    public boolean isBorrowed() { return isBorrowed; }

    public void setTitle(String title) {
        if (title == null || title.isBlank()) throw new IllegalArgumentException("Title cannot be blank");
        this.title = title;
    }

    public void setAuthor(String author) {
        if (author == null || author.isBlank()) throw new IllegalArgumentException("Author cannot be blank");
        this.author = author;
    }

    public void setIsbn(String isbn) {
        if (isbn == null || isbn.isBlank()) throw new IllegalArgumentException("ISBN cannot be blank");
        this.isbn = isbn;
    }

    public void setBorrowed(boolean borrowed) { this.isBorrowed = borrowed; }

    @Override
    public String toString() {
        return "Book{title='" + title + "', author='" + author + "', isbn='" + isbn + "', borrowed=" + isBorrowed + "}";
    }
}

3. Write Member

Same pattern: private fields, validated setters, a constructor that uses them. setBorrowedCount refuses negative numbers, and incrementBorrowedCount() goes through it.

src/lms/model/Member.javaFull reference file
package lms.model;

public class Member {
    private String memberId;
    private String name;
    private int borrowedCount;

    public Member(String memberId, String name) {
        setMemberId(memberId);
        setName(name);
        this.borrowedCount = 0;
    }

    public String getMemberId() { return memberId; }
    public String getName() { return name; }
    public int getBorrowedCount() { return borrowedCount; }

    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 "Member{id='" + memberId + "', name='" + name + "', borrowedCount=" + borrowedCount + "}";
    }
}

4. Write Library

Library has books and members, which is composition. It keeps two private lists and offers methods instead of exposing them.

src/lms/service/Library.javaFull reference file
package lms.service;

import lms.model.Book;
import lms.model.Member;

import java.util.ArrayList;
import java.util.List;

public class Library {
    private final List<Book> books = new ArrayList<>();
    private final List<Member> members = new ArrayList<>();

    public void addBook(Book book) { books.add(book); }
    public void addMember(Member member) { members.add(member); }

    public void listBooks() {
        System.out.println("--- Books in the library ---");
        for (Book b : books) System.out.println(b);
    }

    public void listMembers() {
        System.out.println("--- Registered members ---");
        for (Member m : members) System.out.println(m);
    }

    public List<Book> getBooks() { return books; }
    public List<Member> getMembers() { return members; }
}

5. Test from Main

Always include one bad input, to prove your validation works.

src/lms/Main.javaTest driver
package lms;

import lms.model.Book;
import lms.model.Member;
import lms.service.Library;

public class Main {
    public static void main(String[] args) {
        Library library = new Library();

        library.addBook(new Book("Clean Code", "Robert C. Martin", "978-0132350884"));
        library.addBook(new Book("Effective Java", "Joshua Bloch", "978-0134685991"));
        library.addBook(new Book("The Pragmatic Programmer", "Hunt & Thomas", "978-0135957059"));

        library.addMember(new Member("M001", "Nadeesha Perera"));
        library.addMember(new Member("M002", "Kasun Silva"));

        library.listBooks();
        library.listMembers();

        try {
            library.addBook(new Book("", "Nobody", "000-0000000000"));
        } catch (IllegalArgumentException e) {
            System.out.println("Rejected invalid book: " + e.getMessage());
        }
    }
}
mkdir -p out javac -d out $(find src -name "*.java") java -cp out lms.Main
--- Books in the library --- Book{title='Clean Code', author='Robert C. Martin', isbn='978-0132350884', borrowed=false} Book{title='Effective Java', author='Joshua Bloch', isbn='978-0134685991', borrowed=false} Book{title='The Pragmatic Programmer', author='Hunt & Thomas', isbn='978-0135957059', borrowed=false} --- Registered members --- Member{id='M001', name='Nadeesha Perera', borrowedCount=0} Member{id='M002', name='Kasun Silva', borrowedCount=0} Rejected invalid book: Title cannot be blank

If it does not work

Symptom Fix
package lms.model does not exist Folder names must equal package names; compile from the project root
Prints null as title Missing this. in the setter
No Rejected line The bad book was accepted: the constructor assigns directly instead of calling setTitle

Check Make title public and write book.title = ""; in Main. It compiles and silently breaks the object. That is why we hide data.

Checkpoint: is it done?

  • All fields in all three classes are private
  • Each constructor calls its setters
  • The blank-title book is rejected with Title cannot be blank
  • UML saved as docs/uml-week2.png; PR opened: Week 2: Classes, Objects & Encapsulation