-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Java
-
Section 4: Collections Framework
-
Section 5: Exception Handling
-
Section 6: Generics
-
Section 7: Functional Java
-
Section 8: Concurrency
-
Section 9: I/O and NIO
-
Section 10: JVM Internals
-
Section 11: Modern Java Features
-
Section 12: Build Tools and Project Structure
-
Section 13: Testing
-
Section 14: Databases and Persistence
-
Section 15: Networking
-
Section 16: Design and Best Practices
-
Section 17: Reflection and Annotations
-
Section 18: Logging and Diagnostics
-
Section 19: Date, Time, and Internationalization
-
Section 20: Java Platform Module System
-
Section 21: Security in Java
-
Section 22: Advanced Collections and Data Structures
-
Section 23: More Concurrency Patterns
-
Section 24: Compression, Files, and System Integration
-
Section 25: GUI Programming
-
Section 26: Practical Projects
-
Section 27: Data Structures and Algorithms
-
Section 28: Interview and Algorithm Practice
-
Section 29: JSON and Data Interchange
-
Section 30: More Concurrency Utilities
-
Section 31: More Collections and Streams Practice
-
Section 32: More File and System Programming
-
Section 33: Standard Library Deep Dive
-
Section 34: More Practice and Drills
-
Section 35: More Testing and Quality
-
Section 36: More Design Patterns and Architecture
-
Section 37: Career and Ecosystem
-
Section 38: More OOP and Architecture Practice
-
Section 39: More Enterprise Concepts
-
Section 40: Advanced JavaFX
-
Section 41: More Interview Practice
303: Practice Exercise: Refactoring a Procedural Program into OOP
I see this all the time when developers transition from languages like C or Python to Java: the belief that Object-Oriented Programming is simply a way of organizing your functions into "folders" called classes. You might think that as long as your methods are inside a class, you're "doing OOP."
Classes Aren't Just Folders for Your Methods
Let's look at a piece of code that perfectly illustrates this mistake. Imagine we're building a simple system to track a library's books. A developer with the "class-as-folder" mindset would write something like this:
public class LibrarySystem {
// Data is kept separate from the logic
public static String bookTitle = "The Great Gatsby";
public static boolean isCheckedOut = false;
public static void checkOutBook() {
isCheckedOut = true;
System.out.println(bookTitle + " has been checked out.");
}
public static void returnBook() {
isCheckedOut = false;
System.out.println(bookTitle + " has been returned.");
}
}
Technically, this is in a Java class. But conceptually? This is 100% procedural code. The class is acting as a glorified namespace. We have global state (the static variables) and functions that manipulate that state from the outside. If we wanted to add a second book, this entire structure collapses unless we start managing messy arrays of strings and booleans indexed by integers.
Letting Objects Manage Their Own State
The "Aha!" moment comes when you realize that OOP isn't about where the method lives, but what the method has access to. In a true OOP design, the data and the logic that manipulates that data are bundled together. The object becomes a self-contained unit that is responsible for its own internal consistency.
Instead of a "System" that manages a book, we create a Book object. The logic for checking out a book doesn't belong in a manager class; it belongs to the book itself. Honestly, if you find yourself writing a class that's just a collection of static methods acting on external data, you're just writing C with extra steps.
Here is how we refactor that logic to be truly object-oriented:
public class Book {
private String title;
private boolean isCheckedOut;
public Book(String title) {
this.title = title;
this.isCheckedOut = false;
}
public void checkOut() {
if (this.isCheckedOut) {
System.out.println("Sorry, " + title + " is already gone.");
return;
}
this.isCheckedOut = true;
System.out.println("You have checked out " + title);
}
public void returnBook() {
this.isCheckedOut = false;
System.out.println("Thank you for returning " + title);
}
}
Notice the difference. The Book object now "owns" its state. We've used private modifiers to ensure that no one can manually flip the isCheckedOut flag to true without going through the checkOut() method. This is encapsulation. We aren't just organizing code; we're protecting the integrity of the data.
When you approach your refactoring exercises, ask yourself: "Is this method acting on a piece of data that should actually belong to the object itself?" If the answer is yes, move the method inside that object's class and make the data private. That's the core of the shift from procedural to OOP.
📋 Practical Task
Refactoring the Warehouse Inventory Tracker
Below is a procedural implementation of a warehouse inventory system. Currently, it uses a WarehouseManager class as a "folder" for static methods and arrays to track item names and their quantities. This is fragile and difficult to scale.
public class WarehouseManager {
public static String[] itemNames = {"Laptop", "Mouse", "Keyboard"};
public static int[] itemQuantities = {10, 50, 25};
public static void shipItem(int index, int amount) {
if (itemQuantities[index] >= amount) {
itemQuantities[index] -= amount;
System.out.println("Shipped " + amount + " of " + itemNames[index]);
} else {
System.out.println("Insufficient stock for " + itemNames[index]);
}
}
public static void restockItem(int index, int amount) {
itemQuantities[index] += amount;
System.out.println("Restocked " + itemNames[index] + ". New total: " + itemQuantities[index]);
}
}
Your Task:
- Create a new
Productclass. - Move the
nameandquantityinto theProductclass as private fields. - Move the
shipItemandrestockItemlogic into theProductclass as instance methods (remove the index parameters, as the method will now operate onthisspecific product). - In a
Mainclass, instantiate three differentProductobjects and demonstrate shipping and restocking them.
There are no comments for now.