Home Object-Oriented Programming in Practice
Post
Cancel
Object-Oriented Programming in Practice | SEG

Object-Oriented Programming in Practice

This post explores the practical application of object-oriented programming principles in real-world software development scenarios using Java.

Object Oriented In Practice

When we talk about software design in Java, one of the most powerful paradigms is Object-Oriented Programming (OOP). Instead of thinking about programs only in terms of functions and data, OOP organizes code around objects: entities that represent real-world concepts, each with their own attributes (data) and behaviors (methods).

At the core of OOP are four fundamental principles:

  • Encapsulation – Bundling attributes and methods together inside a single unit: the class. This provides structure and control over how data is accessed or modified.
  • Abstraction – Hiding unnecessary implementation details and exposing only what is relevant. This allows developers to work with simplified interfaces while keeping complexity under the hood.
  • Inheritance – Allowing new classes to derive from existing ones, reusing their attributes and behaviors, and extending or overriding them when needed.
  • Polymorphism – Enabling a single interface or method to represent multiple underlying forms, making code more flexible and reusable.

To illustrate these principles, we’ll build a small project: a Library Management System. This console-based Java application will serve as a practical way to see how each OOP concept translates into real code.

We’ll use IntelliJ IDEA as our development environment, and the project will evolve step by step, with each chapter focusing on a specific aspect of OOP in action.

Building Our Library Management System

This chapter puts the OOP pillars into practice with a small, console-based app we’ll grow over several tasks. If you want a refresher on the principles, skim our OOP overview first. (Object-Oriented Programming)

What we’re building

A lightweight console application where a user can:

  • add books
  • remove books
  • lend books
  • return books
  • list available books

We’ll start with project setup and a runnable main that shows the menu. Next tasks will fill in the domain classes and behaviors.

Core model (preview)

We’ll design three main classes:

  • Book
    • Attributes: title, author, isbn, isLent
    • Methods: lendBook(), returnBook()
  • Library
    • Attributes: List<Book> books
    • Methods: addBook(...), removeBook(...), lendBook(...), returnBook(...), listAvailableBooks(), findBook(...)
  • User (optional, for extra realism)
    • Attributes: name, borrowedBooks
    • Methods: borrowBook(...), returnBook(...)

Implementation Guide: Building Step by Step

Now that we understand what we’re building and why, it’s time to roll up our sleeves and start coding. This section walks you through the complete implementation process, broken down into manageable tasks.

Each task focuses on a specific aspect of the system, demonstrating how OOP principles translate into working code. You’ll see how encapsulation protects data integrity, how abstraction simplifies complex operations, how inheritance promotes code reuse, and how polymorphism enables flexible design.

By the end of this guide, you’ll have a fully functional Library Management System and a deeper understanding of how to apply object-oriented thinking in real-world projects.

Task 1 — Set up the project

1) Prerequisites

  • Install a current JDK (e.g., Temurin, Oracle, or OpenJDK).
  • Verify on your terminal:
1
2
java -version
javac -version

2) Create the project

  • In IntelliJ IDEA: New Project → Java (no framework needed)
  • Name: library-management-system
  • Language level: Java 17+ recommended
  • Source root: src/main/java

Prefer plain Java for now. If you like build tools, Maven/Gradle work fine with the same package layout.

3) Suggested package & folders

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
library-management-system/
├─ src/
│  ├─ LibraryApp.java
│  ├─ books/
│  │  ├─ Book.java
│  │  ├─ PrintedBook.java
│  │  └─ EBook.java
│  ├─ libraries/
│  │  └─ Library.java
│  └─ users/
│     └─ User.java
├─ test/
│  ├─ books/
│  ├─ libraries/
│  └─ users/
└─ .gitignore

4) Bootstrapping the console app

Create src/LibraryApp.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import java.util.Scanner;

public class LibraryApp {
    private static final Scanner SC = new Scanner(System.in);

    public static void main(String[] args) {
        boolean running = true;

        while (running) {
            printMenu();
            String choice = SC.nextLine().trim();

            switch (choice) {
                case "1" -> System.out.println("Add a book");
                case "2" -> System.out.println("Remove a book");
                case "3" -> System.out.println("Lend a book");
                case "4" -> System.out.println("Return a book");
                case "5" -> System.out.println("List available books");
                case "6" -> {
                    System.out.println("Goodbye!");
                    running = false;
                }
                default -> System.out.println("Unknown option. Try again.");
            }
        }
    }

    private static void printMenu() {
        System.out.println("""
                ==============================
                Library Management System
                ------------------------------
                1) Add a book
                2) Remove a book
                3) Lend a book
                4) Return a book
                5) List available books
                6) Exit
                ------------------------------
                Choose an option:
                """);
    }
}

This gives us a clean entry point and a stable interaction loop to plug into as we implement features.

5) Run it

  • In IntelliJ: right-click LibraryApp.javaRun ‘LibraryApp.main()’

Checkpoint: you should see the menu and be able to press 6 to exit.


Task 2 — Designing the Book Class

With the project scaffolded, the next step is to design the Book class. This is the simplest domain entity in our Library Management System, and it’s the perfect way to demonstrate Encapsulation in action.

Role of the Book class

Each Book object represents a single book in the library. It bundles:

  • State (data/attributes): title, author, ISBN, and whether the book is currently lent out.
  • Behavior (methods): lending, returning, and access to its attributes.

By keeping attributes private and exposing public methods to interact with them, we prevent direct external modification of internal state. This is the essence of encapsulation.

Implementation

Create a new file at:

1
src/books/Book.java

Here’s the code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package books;

public abstract class Book {
    private String title;
    private String author;
    private String ISBN;
    private boolean isLent;

    // Constructor
    public Book(String title, String author, String ISBN) {
        this.title = title;
        this.author = author;
        this.ISBN = ISBN;
        this.isLent = false; // By default, a new book is available
    }

    // Abstract discriminator
    public abstract String getBookType();

    // Methods
    public void lendBook() {
        this.isLent = true;
    }

    public void returnBook() {
        this.isLent = false;
    }

    // Getters and Setters
    public String getTitle() { return title; }
    public void setTitle(String title) { this.title = title; }

    public String getAuthor() { return author; }
    public void setAuthor(String author) { this.author = author; }

    public String getISBN() { return ISBN; }
    public void setISBN(String ISBN) { this.ISBN = ISBN; }

    public boolean isLent() { return isLent; }
    public void setLent(boolean lent) { isLent = lent; }

    @Override
    public String toString() {
        return "[" + getBookType() + "] " +
               "Title: " + title +
               ", Author: " + author +
               ", ISBN: " + ISBN +
               (isLent ? " [Lent]" : " [Available]");
    }
}

Key points

  • Encapsulation:

    • All attributes are marked private.
    • Access/modification is only possible through controlled getters and setters.
  • Abstract class design:

    • The abstract keyword prevents direct instantiation of Book objects
    • The abstract method getBookType() forces subclasses to provide their own implementation
    • This ensures we always work with specific book types (PrintedBook, EBook) rather than generic books
  • Constructor:

    • A new book starts as not lent out (isLent = false).
  • Behavior:

    • lendBook() and returnBook() flip the isLent state.
    • External code doesn’t set the boolean directly — it calls these methods.
  • Convenience:

    • toString() provides a formatted representation, making it easy to display book details in the console menu.

What’s next

Now that we have a functional Book class, we can move to the Library class, which manages a collection of books and performs operations such as addBook, removeBook, and findBook.


Task 3 — Designing the Library Class

Now that we have a Book class, it’s time to introduce the Library class. This class acts as the central hub of our application: it manages the collection of books and provides operations for adding, removing, lending, and returning them.

This is where encapsulation and abstraction come together: the Library exposes simple, high-level methods (addBook, lendBook, listAvailableBooks, etc.), while hiding the details of how books are stored and managed internally.

Role of the Library class

  • Attributes:

    • books: a List<Book> that stores all the books in the library.
  • Methods:

    • addBook(Book book) – Adds a new book.
    • removeBook(Book book) – Removes an existing book.
    • lendBook(String isbn) – Marks a book as lent if available.
    • returnBook(String isbn) – Marks a book as returned.
    • listAvailableBooks() – Displays all books not currently lent out.
    • findBook(String isbn) – Finds a book by its ISBN, or null if not found.

Implementation

Create a new file at:

1
src/libraries/Library.java

Here’s the code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
package libraries;

import books.Book;
import java.util.ArrayList;
import java.util.List;

public class Library {
    private List<Book> books;

    // Constructor
    public Library() {
        this.books = new ArrayList<>();
    }

    // Add a new book
    public void addBook(Book book) {
        books.add(book);
    }

    // Remove a book
    public void removeBook(Book book) {
        books.remove(book);
    }

    // Lend a book by ISBN
    public void lendBook(String isbn) {
        Book book = findBook(isbn);
        if (book != null) {
            if (!book.isLent()) {
                book.lendBook();
                System.out.println("Book lent successfully!");
            } else {
                System.out.println("Book is already lent.");
            }
        } else {
            System.out.println("Book not found.");
        }
    }

    // Return a book by ISBN
    public void returnBook(String isbn) {
        Book book = findBook(isbn);
        if (book != null) {
            if (book.isLent()) {
                book.returnBook();
                System.out.println("Book returned successfully!");
            } else {
                System.out.println("Book is not lent.");
            }
        } else {
            System.out.println("Book not found.");
        }
    }

    // List all available books
    public void listAvailableBooks() {
        System.out.println("Available Books:");
        for (Book book : books) {
            if (!book.isLent()) {
                System.out.println(book);
            }
        }
    }

    // Find a book by ISBN
    public Book findBook(String isbn) {
        for (Book book : books) {
            if (book.getISBN().equals(isbn)) {
                return book;
            }
        }
        return null;
    }
}

Key points

  • ArrayList is used to store books dynamically.
  • Encapsulation:

    • The books list is private. External code can’t directly modify it—it must go through public methods.
  • Abstraction:

    • Users of Library don’t need to know how books are stored or searched. They just call lendBook("1234") and get the result.
  • Helper method:

    • findBook(String isbn) centralizes the search logic, avoiding code duplication.

What’s next

With Book and Library in place, our system can store and manipulate a collection of books. In the next chapter, we’ll design the User class, which introduces another level of interaction: users borrowing and returning books.


Task 4 — Designing the User Class

Note: You might notice we jumped from Task 3 to Task 4. This is intentional - we’ll implement inheritance (originally planned for Task 5) as part of the complete application in Task 6.

Our system already supports adding, lending, and returning books through the Library. To make it more realistic, let’s add a User class. This allows us to represent library members, track which books they’ve borrowed, and introduce richer interactions.

This step also prepares us for demonstrating associations between objects in OOP: a User interacts with Book instances, while the Library orchestrates availability.

Role of the User class

  • Attributes

    • name — identifies the user
    • borrowedBooks — the collection of books the user has checked out
  • Methods

    • borrowBook(Book book) — adds the book to the user’s borrowed list
    • returnBook(Book book) — removes the book from their borrowed list
    • listBorrowedBooks() — displays all books the user currently has

Implementation

Create the file:

1
src/users/User.java

Here’s the code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package users;

import books.Book;
import java.util.ArrayList;
import java.util.List;

public class User {
    private String name;
    private List<Book> borrowedBooks;

    // Constructor
    public User(String name) {
        this.name = name;
        this.borrowedBooks = new ArrayList<>();
    }

    // Borrow a book
    public void borrowBook(Book book) {
        borrowedBooks.add(book);
    }

    // Return a book
    public void returnBook(Book book) {
        borrowedBooks.remove(book);
    }

    // List borrowed books
    public void listBorrowedBooks() {
        if (borrowedBooks.isEmpty()) {
            System.out.println(name + " has not borrowed any books.");
        } else {
            System.out.println(name + " has borrowed the following books:");
            for (Book book : borrowedBooks) {
                System.out.println(book);
            }
        }
    }

    // Getters and Setters
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Key points

  • Encapsulation:

    • name and borrowedBooks are private, accessed only through methods.
  • Behavior:

    • Borrowing and returning are simple list operations.
    • The listBorrowedBooks() method provides a user-specific view of borrowed items.
  • Flexibility:

    • This class is independent of Library for now, but in practice, we’d integrate it so Library and User coordinate lending/returning consistently.

What’s next

With User in place, we can:

  1. Integrate users into the main application so lending and returning are tied to a specific user.
  2. Explore Inheritance and Polymorphism by extending Book into EBook and PrintedBook

Task 5 — Inheritance and Polymorphism with Book Subtypes

Before integrating everything in Task 6, let’s implement inheritance and polymorphism by creating concrete subclasses of our abstract Book class. This demonstrates how OOP allows us to share common behavior while specializing for different types.

Why Abstract Classes?

You may have noticed that our Book class is marked as abstract. This means:

  • You cannot create instances of Book directly (no new Book(...))
  • Subclasses must implement the abstract getBookType() method
  • Common behavior is shared through inherited methods like lendBook() and returnBook()

This design forces us to create specific book types while ensuring they all follow the same interface.

Creating PrintedBook

Create src/books/PrintedBook.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package books;

public class PrintedBook extends Book {
    private int numberOfPages;
    private String coverType;

    public PrintedBook(String title, String author, String ISBN, int numberOfPages, String coverType) {
        super(title, author, ISBN);
        this.numberOfPages = numberOfPages;
        this.coverType = coverType;
    }

    @Override
    public String getBookType() {
        return "Printed Book";
    }

    @Override
    public String toString() {
        return super.toString() + ", Number of Pages: " + numberOfPages + ", Cover Type: " + coverType;
    }
}

Creating EBook

Create src/books/EBook.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package books;

public class EBook extends Book {
    private double fileSize;
    private String fileFormat;

    public EBook(String title, String author, String ISBN, double fileSize, String fileFormat) {
        super(title, author, ISBN);
        this.fileSize = fileSize;
        this.fileFormat = fileFormat;
    }

    @Override
    public String getBookType() {
        return "E-Book";
    }

    @Override
    public String toString() {
        return super.toString() + ", File Size: " + fileSize + "MB, File Format: " + fileFormat;
    }
}

Key Inheritance Concepts Demonstrated

  1. Extends keyword: PrintedBook extends Book creates an “is-a” relationship
  2. Super constructor: super(title, author, ISBN) calls the parent class constructor
  3. Method overriding: Both classes override getBookType() and toString()
  4. Inherited behavior: Both classes automatically get lendBook(), returnBook(), and all getters/setters from Book

Polymorphism in Action

Now you can write code like this:

1
2
3
4
5
6
7
8
9
Book book1 = new PrintedBook("1984", "George Orwell", "978-0452284234", 328, "paperback");
Book book2 = new EBook("Digital Fortress", "Dan Brown", "978-0312944926", 15.2, "PDF");

// Same method call, different behavior based on actual object type
System.out.println(book1.toString()); // Shows printed book details
System.out.println(book2.toString()); // Shows e-book details

// Both can be stored in the same collection
List<Book> books = Arrays.asList(book1, book2);

This is polymorphism: the same interface (Book) behaves differently based on the actual object type at runtime.


Task 6 — Integrating Everything Into the Complete Application

Now we’ll connect the dots: wire the User class into the console app so a real person can register, borrow/return books, and view what they’ve checked out. We’ll keep it simple with one active user at a time; you can extend to multiple users later.

What changes

  • Keep a currentUser inside the app.
  • Expand the menu with user-focused options.
  • Delegate borrowing/returning to both Library and User.

Implementation

Create/replace:

1
src/LibraryApp.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
import java.util.Scanner;

import books.Book;
import books.EBook;
import books.PrintedBook;
import libraries.Library;
import users.User;

public class LibraryApp {
    private static Library library = new Library();
    private static User currentUser = null;
    public static Scanner scanner = new Scanner(System.in);

    public static void main(String[] args) {
        while (true) {
            printMenu();

            int choice = readInt(scanner);

            switch (choice) {
                case 1 -> registerUser();
                case 2 -> borrowBookForUser();
                case 3 -> returnBookForUser();
                case 4 -> listBorrowedBooksForUser();
                case 5 -> addPrintedBookToLibrary();
                case 6 -> addEBookToLibrary();
                case 7 -> removeBookFromLibrary();
                case 8 -> lendBookToUser();
                case 9 -> library.listAvailableBooks();
                case 10 -> {
                    System.out.println("Thank you for using the Library Management System!");
                    scanner.close();
                    return;
                }
                default -> System.out.println("Invalid choice. Please try again.");
            }
        }
    }

    private static void printMenu() {
        System.out.println("\nLibrary Management System");
        if (currentUser == null) {
            System.out.println("1. Register as a user");
        } else {
            System.out.println("1. Register as a user (already: " + currentUser.getName() + ")");
        }
        System.out.println("2. Borrow a book");
        System.out.println("3. Return a book");
        System.out.println("4. View my borrowed books");
        System.out.println("5. Add a printed book to the library");
        System.out.println("6. Add an e-book to the library");
        System.out.println("7. Remove a book from the library");
        System.out.println("8. Lend a book to the current user");
        System.out.println("9. List available books in the library");
        System.out.println("10. Exit");
        System.out.print("Enter your choice: ");
    }

    private static void registerUser() {
        if (currentUser == null) {
            System.out.print("Enter your name: ");
            String name = scanner.nextLine();
            currentUser = new User(name);
            System.out.println("User registered successfully!");
        } else {
            System.out.println("User already registered as: " + currentUser.getName());
        }
    }

    private static void borrowBookForUser() {
        if (ensureUser()) {
            System.out.print("Enter book ISBN to borrow: ");
            String isbn = scanner.nextLine();
            Book bookToBorrow = library.findBook(isbn);
            if (bookToBorrow != null && !bookToBorrow.isLent()) {
                library.lendBook(isbn);
                currentUser.borrowBook(bookToBorrow);
                System.out.println("Book borrowed successfully!");
            } else {
                System.out.println("Book not available.");
            }
        }
    }

    private static void returnBookForUser() {
        if (ensureUser()) {
            System.out.print("Enter book ISBN to return: ");
            String isbn = scanner.nextLine();
            Book bookToReturn = library.findBook(isbn);
            if (bookToReturn != null && bookToReturn.isLent()) {
                library.returnBook(isbn);
                currentUser.returnBook(bookToReturn);
                System.out.println("Book returned successfully!");
            } else {
                System.out.println("Book not found or not borrowed by you.");
            }
        }
    }

    private static void listBorrowedBooksForUser() {
        if (ensureUser()) {
            currentUser.listBorrowedBooks();
        }
    }

    private static void removeBookFromLibrary() {
        System.out.print("Enter book ISBN to remove: ");
        String removeIsbn = scanner.nextLine();
        Book bookToRemove = library.findBook(removeIsbn);
        if (bookToRemove != null) {
            library.removeBook(bookToRemove);
            System.out.println("Book removed successfully!");
        } else {
            System.out.println("Book not found.");
        }
    }

    private static void lendBookToUser() {
        if (ensureUser()) {
            System.out.print("Enter book ISBN to lend: ");
            String lendIsbn = scanner.nextLine();
            Book bookToLend = library.findBook(lendIsbn);
            if (bookToLend != null && !bookToLend.isLent()) {
                library.lendBook(lendIsbn);
                currentUser.borrowBook(bookToLend);
                System.out.println("Book lent successfully!");
            } else {
                System.out.println("Book not available.");
            }
        }
    }

    private static boolean ensureUser() {
        if (currentUser == null) {
            System.out.println("Please register as a user first.");
            return false;
        }
        return true;
    }

    private static int readInt(Scanner scanner) {
        while (true) {
            try {
                return Integer.parseInt(scanner.nextLine().trim());
            } catch (NumberFormatException e) {
                System.out.print("Please enter a valid number: ");
            }
        }
    }

    private static void addPrintedBookToLibrary() {
        System.out.print("Enter book title: ");
        String title = scanner.nextLine();
        System.out.print("Enter book author: ");
        String author = scanner.nextLine();
        System.out.print("Enter book ISBN: ");
        String isbn = scanner.nextLine();
        System.out.print("Enter number of pages: ");
        int numberOfPages = readInt(scanner);
        System.out.print("Enter cover type (e.g., hardcover, paperback): ");
        String coverType = scanner.nextLine();

        library.addBook(new PrintedBook(title, author, isbn, numberOfPages, coverType));
        System.out.println("Printed book added successfully!");
    }

    private static void addEBookToLibrary() {
        System.out.print("Enter book title: ");
        String title = scanner.nextLine();
        System.out.print("Enter book author: ");
        String author = scanner.nextLine();
        System.out.print("Enter book ISBN: ");
        String isbn = scanner.nextLine();
        System.out.print("Enter file size (in MB): ");
        double fileSize = Double.parseDouble(scanner.nextLine().trim());
        System.out.print("Enter file format (e.g., PDF, EPUB): ");
        String fileFormat = scanner.nextLine();

        library.addBook(new EBook(title, author, isbn, fileSize, fileFormat));
        System.out.println("E-Book added successfully!");
    }
}
  • The LibraryApp offers a simplified menu interface, hiding complexity under the hood.

  • Inheritance

    • We factored shared behavior into an abstract Book class, then specialized it into PrintedBook and EBook.
    • Both types reuse the base fields (title, author, isbn) while extending with their own attributes (coverType, fileFormat, etc.).
  • Polymorphism

    • We saw polymorphism in action when calling toString() on a Book reference:

      • If the underlying object was an EBook, the e-book details were printed.
      • If it was a PrintedBook, we saw pages and cover type.
    • The same interface (Book) was used, but the behavior changed depending on the actual object type.

What we built

  • Book (abstract) — base for all book types
  • PrintedBook / EBook — concrete subtypes demonstrating inheritance + polymorphism
  • Library — collection manager that handles add, remove, find, lend, return
  • User — a person borrowing/returning books
  • LibraryApp — the console interface tying it all together

Task 7 — Testing and Test Coverage

A crucial aspect of any well-designed software system is comprehensive testing. Our Library Management System includes unit tests that demonstrate best practices for testing object-oriented code.

Why Testing Matters

Test coverage ensures that:

  • Code reliability - Each class and method behaves as expected under various conditions
  • Regression prevention - Changes to the codebase don’t break existing functionality
  • Documentation - Tests serve as living examples of how classes should be used
  • Refactoring safety - You can confidently modify internal implementations knowing tests will catch breaking changes

Our Test Structure

The project includes JUnit tests organized by package:

1
2
3
4
5
6
7
test/
├─ books/
│  └─ PrintedBookTest.java
├─ libraries/
│  └─ LibraryTest.java
└─ users/
   └─ UserTest.java

Testing Approaches Used

Unit Testing with JUnit:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void newBookIsNotLentTest() {
    // arrange
    PrintedBook printedBook = new PrintedBook(
        "Title 1", "Author 1", "1234", 100, "hardcover"
    );
    
    // act
    String bookInfo = printedBook.toString();
    
    // assert
    Assert.assertTrue(bookInfo.contains("Is lent: no"));
}

Mocking with Mockito:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void findBookWhenAddedToBooksTest() {
    // arrange
    String ISBN = "1234";
    when(mockedBook.getISBN()).thenReturn(ISBN);
    Library library = new Library();
    
    // act
    library.addBook(mockedBook);
    Book actualBook = library.findBook(ISBN);
    
    // assert
    Assert.assertEquals(mockedBook, actualBook);
}

Key Testing Benefits

  • Isolated testing - Each class is tested independently using mocks when needed
  • Behavior verification - Tests focus on what the code does, not just how it’s implemented
  • Edge case coverage - Tests verify both normal operations and boundary conditions
  • Maintainable test code - Clear arrange-act-assert structure makes tests easy to understand

Having comprehensive test coverage gives you confidence that your object-oriented design works correctly and can evolve safely over time.

Final thoughts

This was a deliberately simple design, meant to illustrate core OOP ideas rather than cover every real-world detail. In future iterations, we could:

  • Add persistence (save/load books and users to a file or database)
  • Support multiple users with roles (Member, Librarian)
  • Introduce an AudioBook subclass for further inheritance/polymorphism examples
  • Add exception handling and validations for robustness

But even at this stage, the design demonstrates the power of OOP: modular, extensible, and reusable code.


👉 That concludes our series on Object-Oriented Programming in Java with the Library Management System.

📦 Full source code available here: GitHub Repository — Library Management System

This post is licensed under CC BY 4.0 by the author.