Putting It All Together: A Mini Design Exercise
What is it? (The Analogy)
You have learned 7 separate OOP concepts, like 7 ingredients sitting on a kitchen counter. Each one is useful on its own, but the REAL magic happens when you combine them into a complete dish. A chef does not just serve flour, eggs, sugar, and butter separately -- they combine them with skill and intention into a cake. Similarly, a good software designer does not just "use inheritance" or "add an interface" randomly. They combine ALL the OOP tools purposefully to create a clean, extensible, maintainable system.
This lesson is your first time being the chef. We are going to design a Library Management System from scratch, and at every step, we will consciously apply the concepts you have learned. You will see how classes, encapsulation, inheritance, polymorphism, abstraction, interfaces, and composition all work TOGETHER in harmony. This is the bridge from "I know OOP concepts" to "I can DESIGN with OOP."
Think of this as your graduation exercise. After this, you will look at LLD problems with completely different eyes.
Why do we need it?
Because knowing concepts in isolation is not enough. In real interviews and real jobs, you will never be asked "write a class with encapsulation." You will be asked "design a library system" or "design a parking lot" and you have to figure out WHICH concepts to apply WHERE. The challenge is not knowing the tools -- it is knowing when and how to combine them.
Let's build this together, step by step.
How it works -- Step by Step
Here is our design process:
- Identify the nouns -- These become classes (Book, Member, Librarian, Library).
- Identify the adjectives/attributes -- These become fields (title, author, isbn, isAvailable).
- Identify the verbs -- These become methods (borrow, return, search, pay).
- Identify IS-A relationships -- These suggest inheritance (EBook IS-A Book, StudentMember IS-A Member).
- Identify CAN-DO capabilities -- These suggest interfaces (Searchable, Borrowable, Notifiable).
- Identify HAS-A relationships -- These suggest composition (Library HAS-A catalog, Member HAS-A list of borrowed books).
- Apply encapsulation everywhere -- Make fields private, validate in setters, protect internal state.
- Use abstraction for varying behavior -- Where subclasses differ, use abstract methods.
Let's Build It Together
Step 1: Start with the core concept -- what IS in a library?
Concept Used: Classes & Objects, Encapsulation
1// The most fundamental entity -- a library item
2// We use ENCAPSULATION: all fields private, controlled access
3public class Book {
4 private String isbn;
5 private String title;
6 private String author;
7 private int publicationYear;
8 private boolean isAvailable;
9
10 public Book(String isbn, String title, String author, int publicationYear) {
11 // Validate in the constructor!
12 if (isbn == null || isbn.trim().isEmpty()) {
13 throw new IllegalArgumentException("ISBN cannot be empty");
14 }
15 if (title == null || title.trim().isEmpty()) {
16 throw new IllegalArgumentException("Title cannot be empty");
17 }
18 this.isbn = isbn;
19 this.title = title;
20 this.author = author;
21 this.publicationYear = publicationYear;
22 this.isAvailable = true; // New books start as available
23 }
24
25 // Controlled access -- no setIsbn() because ISBN should never change!
26 public String getIsbn() { return isbn; }
27 public String getTitle() { return title; }
28 public String getAuthor() { return author; }
29 public int getPublicationYear() { return publicationYear; }
30 public boolean isAvailable() { return isAvailable; }
31
32 // Behavior methods instead of raw setters
33 public void markBorrowed() {
34 if (!isAvailable) {
35 throw new IllegalStateException("Book is already borrowed!");
36 }
37 isAvailable = false;
38 }
39
40 public void markReturned() {
41 if (isAvailable) {
42 throw new IllegalStateException("Book was not borrowed!");
43 }
44 isAvailable = true;
45 }
46
47 @Override
48 public String toString() {
49 String status = isAvailable ? "Available" : "Borrowed";
50 return "[" + isbn + "] " + title + " by " + author
51 + " (" + publicationYear + ") - " + status;
52 }
53}Design Decision: Notice we do NOT have a \setIsbn()\ method. An ISBN is immutable -- it identifies the book forever. We DO have \markBorrowed()\ and \markReturned()\ instead of \setIsAvailable(boolean)\ because these methods enforce business rules (you cannot borrow an already-borrowed book). This is encapsulation done RIGHT.
Step 2: Not all library items are books -- use Inheritance & Abstraction
Concepts Used: Inheritance, Abstraction, Polymorphism
1// ABSTRACT CLASS -- the base for all library items
2// Some items are books, some are DVDs, some are magazines
3public abstract class LibraryItem {
4 private String id;
5 private String title;
6 private int publicationYear;
7 private boolean isAvailable;
8
9 public LibraryItem(String id, String title, int publicationYear) {
10 this.id = id;
11 this.title = title;
12 this.publicationYear = publicationYear;
13 this.isAvailable = true;
14 }
15
16 // ABSTRACT: each item type displays differently
17 public abstract String getItemType();
18
19 // ABSTRACT: each item type has different borrowing durations
20 public abstract int getMaxBorrowDays();
21
22 // Concrete shared methods
23 public void markBorrowed() {
24 if (!isAvailable) {
25 throw new IllegalStateException(title + " is already borrowed!");
26 }
27 isAvailable = false;
28 }
29
30 public void markReturned() {
31 isAvailable = true;
32 }
33
34 // Getters
35 public String getId() { return id; }
36 public String getTitle() { return title; }
37 public boolean isAvailable() { return isAvailable; }
38
39 @Override
40 public String toString() {
41 return "[" + getItemType() + "] " + title + " (max "
42 + getMaxBorrowDays() + " days)";
43 }
44}1// CONCRETE subclass: Book
2public class Book extends LibraryItem {
3 private String author;
4 private String isbn;
5
6 public Book(String isbn, String title, String author, int year) {
7 super(isbn, title, year);
8 this.isbn = isbn;
9 this.author = author;
10 }
11
12 @Override
13 public String getItemType() { return "Book"; }
14
15 @Override
16 public int getMaxBorrowDays() { return 21; } // 3 weeks
17
18 public String getAuthor() { return author; }
19}1// CONCRETE subclass: DVD
2public class DVD extends LibraryItem {
3 private String director;
4 private int runtimeMinutes;
5
6 public DVD(String id, String title, String director,
7 int year, int runtimeMinutes) {
8 super(id, title, year);
9 this.director = director;
10 this.runtimeMinutes = runtimeMinutes;
11 }
12
13 @Override
14 public String getItemType() { return "DVD"; }
15
16 @Override
17 public int getMaxBorrowDays() { return 7; } // 1 week only!
18
19 public String getDirector() { return director; }
20}1// CONCRETE subclass: Magazine
2public class Magazine extends LibraryItem {
3 private int issueNumber;
4
5 public Magazine(String id, String title, int year, int issueNumber) {
6 super(id, title, year);
7 this.issueNumber = issueNumber;
8 }
9
10 @Override
11 public String getItemType() { return "Magazine"; }
12
13 @Override
14 public int getMaxBorrowDays() { return 14; } // 2 weeks
15}Design Decision: We made \LibraryItem\ abstract because you should never create a "generic" library item -- it is always specifically a Book, DVD, or Magazine. The abstract methods \getItemType()\ and \getMaxBorrowDays()\ force each subclass to define its own values. Polymorphism lets us treat them all as \LibraryItem\ in collections and methods.
Step 3: Define capabilities with Interfaces
Concept Used: Interfaces
// Interface: things that can be searched
public interface Searchable {
boolean matchesQuery(String query);
}// Interface: things that can send notifications
public interface Notifiable {
void sendNotification(String message);
String getContactInfo();
}// Interface: things that can have late fees calculated
public interface FeeCalculable {
double calculateLateFee(int daysOverdue);
}Now let's make our classes implement these interfaces:
1// Book now also IS Searchable and FeeCalculable
2public class Book extends LibraryItem implements Searchable, FeeCalculable {
3 private String author;
4 private String isbn;
5
6 // ... constructor same as before ...
7
8 @Override
9 public boolean matchesQuery(String query) {
10 String q = query.toLowerCase();
11 return getTitle().toLowerCase().contains(q)
12 || author.toLowerCase().contains(q)
13 || isbn.contains(q);
14 }
15
16 @Override
17 public double calculateLateFee(int daysOverdue) {
18 return daysOverdue * 0.25; // 25 cents per day
19 }
20
21 @Override
22 public String getItemType() { return "Book"; }
23
24 @Override
25 public int getMaxBorrowDays() { return 21; }
26
27 public String getAuthor() { return author; }
28}1// DVD IS also FeeCalculable but with a DIFFERENT rate!
2public class DVD extends LibraryItem implements Searchable, FeeCalculable {
3 private String director;
4 private int runtimeMinutes;
5
6 // ... constructor same as before ...
7
8 @Override
9 public boolean matchesQuery(String query) {
10 String q = query.toLowerCase();
11 return getTitle().toLowerCase().contains(q)
12 || director.toLowerCase().contains(q);
13 }
14
15 @Override
16 public double calculateLateFee(int daysOverdue) {
17 return daysOverdue * 1.00; // $1 per day -- DVDs are expensive to replace!
18 }
19
20 @Override
21 public String getItemType() { return "DVD"; }
22
23 @Override
24 public int getMaxBorrowDays() { return 7; }
25
26 public String getDirector() { return director; }
27}Step 4: Build the Member system with Composition
Concepts Used: Composition, Encapsulation
1// A library member HAS-A list of borrowed items (composition!)
2public class Member implements Notifiable {
3 private String memberId;
4 private String name;
5 private String email;
6 private List<LibraryItem> borrowedItems; // HAS-A relationship!
7 private static final int MAX_BORROW_LIMIT = 5;
8
9 public Member(String memberId, String name, String email) {
10 this.memberId = memberId;
11 this.name = name;
12 this.email = email;
13 this.borrowedItems = new ArrayList<>();
14 }
15
16 public boolean canBorrow() {
17 return borrowedItems.size() < MAX_BORROW_LIMIT;
18 }
19
20 public void borrowItem(LibraryItem item) {
21 if (!canBorrow()) {
22 throw new IllegalStateException(
23 name + " has reached the borrowing limit of " + MAX_BORROW_LIMIT);
24 }
25 if (!item.isAvailable()) {
26 throw new IllegalStateException(item.getTitle() + " is not available!");
27 }
28 item.markBorrowed();
29 borrowedItems.add(item);
30 System.out.println(name + " borrowed: " + item);
31 }
32
33 public void returnItem(LibraryItem item) {
34 if (!borrowedItems.contains(item)) {
35 throw new IllegalArgumentException(
36 name + " did not borrow " + item.getTitle());
37 }
38 item.markReturned();
39 borrowedItems.remove(item);
40 System.out.println(name + " returned: " + item);
41 }
42
43 // Notifiable interface implementation
44 @Override
45 public void sendNotification(String message) {
46 System.out.println("[EMAIL to " + email + "] " + message);
47 }
48
49 @Override
50 public String getContactInfo() {
51 return email;
52 }
53
54 // Return a defensive copy!
55 public List<LibraryItem> getBorrowedItems() {
56 return new ArrayList<>(borrowedItems);
57 }
58
59 public String getName() { return name; }
60 public String getMemberId() { return memberId; }
61
62 @Override
63 public String toString() {
64 return name + " (ID: " + memberId + ") - "
65 + borrowedItems.size() + "/" + MAX_BORROW_LIMIT + " items borrowed";
66 }
67}Step 5: The Library class brings it all together with Composition
Concepts Used: Composition, Polymorphism, Interfaces
1// The Library HAS-A collection of items and HAS-A collection of members
2// It does NOT inherit from anything -- pure composition!
3public class Library {
4 private String name;
5 private List<LibraryItem> catalog; // HAS-A catalog (composition)
6 private List<Member> members; // HAS-A member list (composition)
7
8 public Library(String name) {
9 this.name = name;
10 this.catalog = new ArrayList<>();
11 this.members = new ArrayList<>();
12 }
13
14 public void addItem(LibraryItem item) {
15 catalog.add(item);
16 System.out.println("Added to " + name + ": " + item);
17 }
18
19 public void registerMember(Member member) {
20 members.add(member);
21 member.sendNotification("Welcome to " + name + ", " + member.getName() + "!");
22 }
23
24 // POLYMORPHISM in action -- works with ANY Searchable LibraryItem!
25 public List<LibraryItem> search(String query) {
26 List<LibraryItem> results = new ArrayList<>();
27 for (LibraryItem item : catalog) {
28 if (item instanceof Searchable) {
29 Searchable searchable = (Searchable) item;
30 if (searchable.matchesQuery(query)) {
31 results.add(item);
32 }
33 }
34 }
35 return results;
36 }
37
38 // List all available items -- polymorphism handles all types!
39 public void listAvailableItems() {
40 System.out.println("\n--- Available items at " + name + " ---");
41 for (LibraryItem item : catalog) {
42 if (item.isAvailable()) {
43 System.out.println(" " + item);
44 }
45 }
46 System.out.println("---");
47 }
48
49 // Notify all members -- uses the Notifiable interface
50 public void notifyAllMembers(String message) {
51 for (Member member : members) {
52 member.sendNotification(message);
53 }
54 }
55}Step 6: Let's see it ALL in action!
1public class LibraryDemo {
2 public static void main(String[] args) {
3
4 // ===== Create the library =====
5 Library library = new Library("Springfield Public Library");
6
7 // ===== Add items (POLYMORPHISM -- all stored as LibraryItem) =====
8 Book book1 = new Book("978-0-13-468599-1",
9 "Head First Java", "Kathy Sierra", 2005);
10 Book book2 = new Book("978-0-596-00712-6",
11 "Head First Design Patterns", "Eric Freeman", 2004);
12 DVD dvd1 = new DVD("DVD-001",
13 "The Matrix", "Wachowski Sisters", 1999, 136);
14 Magazine mag1 = new Magazine("MAG-001",
15 "Java Weekly", 2024, 42);
16
17 library.addItem(book1); // All treated as LibraryItem!
18 library.addItem(book2);
19 library.addItem(dvd1);
20 library.addItem(mag1);
21
22 // ===== Register members =====
23 Member alice = new Member("M001", "Alice", "alice@email.com");
24 Member bob = new Member("M002", "Bob", "bob@email.com");
25 library.registerMember(alice);
26 library.registerMember(bob);
27
28 // ===== List available items =====
29 library.listAvailableItems();
30
31 // ===== Search (uses Searchable interface -- POLYMORPHISM) =====
32 System.out.println("\nSearching for \"Head First\":");
33 List<LibraryItem> results = library.search("Head First");
34 for (LibraryItem item : results) {
35 System.out.println(" Found: " + item);
36 }
37
38 // ===== Borrow items =====
39 alice.borrowItem(book1);
40 alice.borrowItem(dvd1);
41 bob.borrowItem(book2);
42
43 // ===== Check availability after borrowing =====
44 library.listAvailableItems();
45
46 // ===== Return an item =====
47 alice.returnItem(book1);
48 library.listAvailableItems();
49
50 // ===== Late fee calculation (POLYMORPHISM via FeeCalculable) =====
51 System.out.println("\n--- Late Fee Simulation ---");
52 int daysLate = 5;
53 System.out.println("Book 5 days late: $"
54 + String.format("%.2f", book1.calculateLateFee(daysLate)));
55 System.out.println("DVD 5 days late: $"
56 + String.format("%.2f", dvd1.calculateLateFee(daysLate)));
57
58 // ===== Notify all members =====
59 library.notifyAllMembers("Library will be closed on Monday for maintenance.");
60
61 // ===== Show member status =====
62 System.out.println("\n" + alice);
63 System.out.println(bob);
64 }
65}Visual Mental Model
1 +----------------------------------------------------------+
2 | Library (COMPOSITION) |
3 | HAS-A catalog (List<LibraryItem>) |
4 | HAS-A members (List<Member>) |
5 +----------------------------------------------------------+
6 | |
7 v v
8 +-------------------+ +------------------+
9 | LibraryItem | | Member |
10 | <<abstract>> | |------------------|
11 |-------------------| | - memberId |
12 | - id, title, year | | - name, email |
13 | - isAvailable | | - borrowedItems | <-- COMPOSITION
14 |-------------------| | (List<LibItem>)| (HAS-A)
15 | + getItemType()* | |------------------|
16 | + getMaxBorrow()* | | + borrowItem() |
17 | + markBorrowed() | | + returnItem() |
18 +-------------------+ +------------------+
19 / | \ implements Notifiable
20 / | \
21 Book DVD Magazine
22 (21d) (7d) (14d)
23
24 implements: implements:
25 Searchable Searchable
26 FeeCalculable FeeCalculable
27
28 OOP CONCEPTS USED:
29 [1] Classes & Objects -- Book, Member, Library, DVD, Magazine
30 [2] Encapsulation -- Private fields, validation, defensive copies
31 [3] Inheritance -- Book/DVD/Magazine extend LibraryItem
32 [4] Polymorphism -- List<LibraryItem> holds all types, correct method runs
33 [5] Abstraction -- LibraryItem is abstract, defines contract
34 [6] Interfaces -- Searchable, Notifiable, FeeCalculable for capabilities
35 [7] Composition -- Library HAS items/members, Member HAS borrowed itemsReal-World Analogy Recap
Designing a system with OOP is like being an architect for a building. You do not just randomly throw bricks together. You plan: "The foundation (classes) supports everything. The walls (encapsulation) protect the interior. The elevator shafts (inheritance) provide shared vertical structure. The electrical outlets (interfaces) are standardized ports any device can plug into. The modular furniture (composition) can be rearranged without rebuilding the room." Each concept plays a specific, intentional role. Together, they create a building that is safe, functional, and adaptable to change.
Common Mistakes & Gotchas
- Jumping to code before designing: Always sketch the classes, relationships, and interfaces FIRST. Five minutes of design saves hours of refactoring.
- Using inheritance when composition is better: "Member HAS borrowed items" is composition, not inheritance. A Member is NOT a list of books!
- Making everything concrete: If you have multiple item types, use an abstract base class or interface. Do not write a single \
Item\class with a \type\string field. - Forgetting about polymorphism opportunities: If you find yourself writing \
instanceof\checks everywhere, you probably missed an opportunity to use polymorphism via a shared interface or abstract method. - Not thinking about extensibility: What happens when someone wants to add a "Vinyl Record" or "Audiobook" to the library? With our design, they just create a new class extending \
LibraryItem\. No existing code changes. THAT is the payoff of good OOP design. - Over-engineering: Not everything needs to be abstract. If you only have one type of notification (email), a \
Notifiable\interface might be overkill. Design for what you need NOW, with doors open for the future.
Interview Tip
In LLD interviews, the interviewer is not just checking if your code compiles. They are evaluating your DESIGN THINKING. Show that you: (1) Identify abstractions early -- "I see that Book, DVD, and Magazine are all Library Items." (2) Choose the right relationship -- "A Library HAS items (composition), not IS-A item (inheritance)." (3) Use interfaces for capabilities -- "Not all items are Searchable, so I will make that an interface." (4) Apply encapsulation consistently -- "I will validate in constructors and expose behavior methods instead of raw setters." Walk through your reasoning OUT LOUD. The thought process matters more than the final code.
Quick Quiz
- If we want to add a new item type called \
Audiobook\that extends \LibraryItem\and also implements \Playable\, what classes need to change? (Hint: this is the test of our design!) - Why did we make \
Library\use composition (HAS-A List of items) rather than extending some \Collection\class? - Identify each of the 7 OOP concepts in our design and explain WHY we used each one where we did. Could any of the decisions be made differently?
Summary -- Key Takeaways
- Good OOP design combines ALL the concepts together: classes for structure, encapsulation for safety, inheritance for shared behavior, polymorphism for flexibility, abstraction for contracts, interfaces for capabilities, and composition for building complex objects from simpler ones.
- Design BEFORE code: Identify nouns (classes), verbs (methods), IS-A relationships (inheritance), CAN-DO capabilities (interfaces), and HAS-A relationships (composition).
- The ultimate test of a good design: can you add a new type or feature without modifying existing code? If yes, your OOP design is solid.
- This is the foundation of all Low-Level Design. Every LLD problem (parking lot, elevator, chess, etc.) is built on these exact same principles. Master them here, and every future problem becomes easier.