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()
- Attributes:
- Library
- Attributes:
List<Book> books - Methods:
addBook(...),removeBook(...),lendBook(...),returnBook(...),listAvailableBooks(),findBook(...)
- Attributes:
- User (optional, for extra realism)
- Attributes:
name,borrowedBooks - Methods:
borrowBook(...),returnBook(...)
- Attributes:
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.java→ Run ‘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.
- All attributes are marked
Abstract class design:
- The
abstractkeyword prevents direct instantiation ofBookobjects - 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
- The
Constructor:
- A new book starts as not lent out (
isLent = false).
- A new book starts as not lent out (
Behavior:
lendBook()andreturnBook()flip theisLentstate.- 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: aList<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, ornullif 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
bookslist is private. External code can’t directly modify it—it must go through public methods.
- The
Abstraction:
- Users of
Librarydon’t need to know how books are stored or searched. They just calllendBook("1234")and get the result.
- Users of
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 userborrowedBooks— the collection of books the user has checked out
Methods
borrowBook(Book book)— adds the book to the user’s borrowed listreturnBook(Book book)— removes the book from their borrowed listlistBorrowedBooks()— 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:
nameandborrowedBooksare 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
Libraryfor now, but in practice, we’d integrate it soLibraryandUsercoordinate lending/returning consistently.
- This class is independent of
What’s next
With User in place, we can:
- Integrate users into the main application so lending and returning are tied to a specific user.
- Explore Inheritance and Polymorphism by extending
BookintoEBookandPrintedBook
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
Bookdirectly (nonew Book(...)) - Subclasses must implement the abstract
getBookType()method - Common behavior is shared through inherited methods like
lendBook()andreturnBook()
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
- Extends keyword:
PrintedBook extends Bookcreates an “is-a” relationship - Super constructor:
super(title, author, ISBN)calls the parent class constructor - Method overriding: Both classes override
getBookType()andtoString() - Inherited behavior: Both classes automatically get
lendBook(),returnBook(), and all getters/setters fromBook
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
currentUserinside the app. - Expand the menu with user-focused options.
- Delegate borrowing/returning to both
LibraryandUser.
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
LibraryAppoffers a simplified menu interface, hiding complexity under the hood.Inheritance
- We factored shared behavior into an abstract
Bookclass, then specialized it intoPrintedBookandEBook. - Both types reuse the base fields (
title,author,isbn) while extending with their own attributes (coverType,fileFormat, etc.).
- We factored shared behavior into an abstract
Polymorphism
We saw polymorphism in action when calling
toString()on aBookreference:- If the underlying object was an
EBook, the e-book details were printed. - If it was a
PrintedBook, we saw pages and cover type.
- If the underlying object was an
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
AudioBooksubclass 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
