- Shift Elevate
- Posts
- Large Class: Extract Class Refactoring | Clean Code
Large Class: Extract Class Refactoring | Clean Code
Large Class is a code smell where a class has grown too large and tries to do too much.
We will see how we can refactor using the Extract Class technique for better maintainability and single responsibility.
Clean Code Reference
⚠️ Code Smell: Large Class
✅ Refactoring: Extract Class
🎯 Goal: Single Responsibility Principle and focused classes
The Large Class code smell occurs when a class becomes too large and takes on too many responsibilities, violating the Single Responsibility Principle. The Extract Class refactoring technique breaks down large classes into smaller, focused classes that each have a single, well-defined responsibility.
The Code Smell: Large Class
Large Class is one of the most common code smells that indicates a violation of the Single Responsibility Principle. When a class grows too large, it becomes difficult to understand, test, and maintain. Large classes often contain multiple unrelated responsibilities, making them fragile and prone to bugs.
Symptoms | Impact |
|---|---|
Too many fields and methods | Difficult to understand |
Multiple unrelated responsibilities | Hard to test and maintain |
Frequent changes affecting many methods | Higher bug risk |
Here's a typical example of Large Class:
public class CustomerManager {
// Customer data fields
private String customerId;
private String firstName;
private String lastName;
private String email;
private String phone;
private String address;
private String city;
private String state;
private String zipCode;
private String country;
// Account management fields
private double accountBalance;
private String accountStatus;
private Date accountCreatedDate;
private Date lastLoginDate;
private int loyaltyPoints;
// Notification preferences
private boolean emailNotifications;
private boolean smsNotifications;
private boolean pushNotifications;
private String preferredLanguage;
// What does this massive class do? Everything!
// Customer data methods
public void updatePersonalInfo(String firstName, String lastName, String email) {
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
validateEmail(email);
logPersonalInfoUpdate();
}
public void updateAddress(String address, String city, String state, String zipCode, String country) {
this.address = address;
this.city = city;
this.state = state;
this.zipCode = zipCode;
this.country = country;
validateAddress();
logAddressUpdate();
}
// Account management methods
public void processPayment(double amount) {
if (accountBalance >= amount) {
accountBalance -= amount;
updateLoyaltyPoints(amount);
logTransaction(amount);
sendPaymentConfirmation();
} else {
throw new InsufficientFundsException("Insufficient account balance");
}
}
public void addFunds(double amount) {
accountBalance += amount;
logTransaction(amount);
sendBalanceUpdateNotification();
}
public void updateAccountStatus(String status) {
this.accountStatus = status;
logStatusChange(status);
if ("SUSPENDED".equals(status)) {
sendAccountSuspensionNotification();
}
}
// Notification methods
public void updateNotificationPreferences(boolean email, boolean sms, boolean push) {
this.emailNotifications = email;
this.smsNotifications = sms;
this.pushNotifications = push;
logPreferenceUpdate();
}
public void sendNotification(String message, String type) {
if ("EMAIL".equals(type) && emailNotifications) {
sendEmailNotification(message);
} else if ("SMS".equals(type) && smsNotifications) {
sendSMSNotification(message);
} else if ("PUSH".equals(type) && pushNotifications) {
sendPushNotification(message);
}
}
// Private helper methods (too many!)
private void validateEmail(String email) { /* validation logic */ }
private void validateAddress() { /* validation logic */ }
private void logPersonalInfoUpdate() { /* logging logic */ }
private void logAddressUpdate() { /* logging logic */ }
private void logTransaction(double amount) { /* logging logic */ }
private void logStatusChange(String status) { /* logging logic */ }
private void logPreferenceUpdate() { /* logging logic */ }
private void updateLoyaltyPoints(double amount) { /* loyalty logic */ }
private void sendPaymentConfirmation() { /* notification logic */ }
private void sendBalanceUpdateNotification() { /* notification logic */ }
private void sendAccountSuspensionNotification() { /* notification logic */ }
private void sendEmailNotification(String message) { /* email logic */ }
private void sendSMSNotification(String message) { /* SMS logic */ }
private void sendPushNotification(String message) { /* push logic */ }
}In this example, the CustomerManager class is trying to do everything: manage customer data, handle account operations, and manage notifications. This violates the Single Responsibility Principle and makes the class difficult to understand, test, and maintain.
The Refactoring: Extract Class
The Extract Class refactoring technique breaks down large classes into smaller, focused classes that each have a single, well-defined responsibility. This improves code organization, testability, and maintainability.
Step by Step Refactoring Process:
Identify distinct responsibilities within the large class.
Group related fields and methods that belong together.
Create new classes for each identified responsibility.
Move fields and methods to their appropriate classes.
Update the original class to use the new classes through composition.
Here's the refactored version:
The large class has been broken down into four focused classes:
CustomerManager - Main coordinator class
CustomerProfile - Manages customer personal information
AccountManager - Handles account operations
NotificationManager - Manages notification preferences and sending
1. CustomerManager - Main Coordinator Class
// Main class now focuses on coordinating other classes
public class CustomerManager {
private CustomerProfile customerProfile;
private AccountManager accountManager;
private NotificationManager notificationManager;
public CustomerManager(String customerId) {
this.customerProfile = new CustomerProfile(customerId);
this.accountManager = new AccountManager();
this.notificationManager = new NotificationManager();
}
public void updatePersonalInfo(String firstName, String lastName, String email) {
customerProfile.updatePersonalInfo(firstName, lastName, email);
}
public void updateAddress(String address, String city, String state, String zipCode, String country) {
customerProfile.updateAddress(address, city, state, zipCode, country);
}
public void processPayment(double amount) {
accountManager.processPayment(amount);
notificationManager.sendPaymentConfirmation();
}
}2. CustomerProfile - Manages Customer Personal Information
// Focused class for customer profile data
public class CustomerProfile {
private String customerId;
private String firstName;
private String lastName;
private String email;
private String phone;
private String address;
private String city;
private String state;
private String zipCode;
private String country;
public CustomerProfile(String customerId) {
this.customerId = customerId;
}
public void updatePersonalInfo(String firstName, String lastName, String email) {
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
validateEmail(email);
logPersonalInfoUpdate();
}
public void updateAddress(String address, String city, String state, String zipCode, String country) {
this.address = address;
this.city = city;
this.state = state;
this.zipCode = zipCode;
this.country = country;
validateAddress();
logAddressUpdate();
}
private void validateEmail(String email) { /* validation logic */ }
private void validateAddress() { /* validation logic */ }
private void logPersonalInfoUpdate() { /* logging logic */ }
private void logAddressUpdate() { /* logging logic */ }
// Getters and other profile-related methods
}3. AccountManager - Handles Account Operations
// Focused class for account operations
public class AccountManager {
private double accountBalance;
private String accountStatus;
private Date accountCreatedDate;
private Date lastLoginDate;
private int loyaltyPoints;
public void processPayment(double amount) {
if (accountBalance >= amount) {
accountBalance -= amount;
logTransaction(amount);
} else {
throw new InsufficientFundsException("Insufficient account balance");
}
}
public void addFunds(double amount) {
accountBalance += amount;
logTransaction(amount);
}
public void updateAccountStatus(String status) {
this.accountStatus = status;
logStatusChange(status);
}
public void updateLoyaltyPoints(double amount) {
loyaltyPoints += (int) (amount * 0.1); // 10% of amount as points
}
private void logTransaction(double amount) { /* logging logic */ }
private void logStatusChange(String status) { /* logging logic */ }
// Getters and other account-related methods
}4. NotificationManager - Manages Notification Preferences and Sending
// Focused class for notifications
public class NotificationManager {
private boolean emailNotifications;
private boolean smsNotifications;
private boolean pushNotifications;
private String preferredLanguage;
public NotificationManager() {
this.emailNotifications = true;
this.smsNotifications = false;
this.pushNotifications = true;
this.preferredLanguage = "EN";
}
public void updateNotificationPreferences(boolean email, boolean sms, boolean push) {
this.emailNotifications = email;
this.smsNotifications = sms;
this.pushNotifications = push;
logPreferenceUpdate();
}
public void sendNotification(String message, String type) {
if ("EMAIL".equals(type) && emailNotifications) {
sendEmailNotification(message);
} else if ("SMS".equals(type) && smsNotifications) {
sendSMSNotification(message);
} else if ("PUSH".equals(type) && pushNotifications) {
sendPushNotification(message);
}
}
public void sendPaymentConfirmation() {
sendNotification("Payment processed successfully", "EMAIL");
}
private void logPreferenceUpdate() { /* logging logic */ }
private void sendEmailNotification(String message) { /* email logic */ }
private void sendSMSNotification(String message) { /* SMS logic */ }
private void sendPushNotification(String message) { /* push logic */ }
}Usage Example - Putting It All Together
public class Main {
public static void main(String[] args) {
// Create CustomerManager with refactored structure
CustomerManager customerManager = new CustomerManager("CUST002");
// Same operations, but now delegated to appropriate classes
customerManager.updatePersonalInfo("Jane", "Smith", "[email protected]");
customerManager.updateAddress("456 Oak Ave", "Chicago", "IL", "60601", "USA");
customerManager.addFunds(750.0);
customerManager.processPayment(200.0);
customerManager.addOrder("ORDER003", 120.0);
customerManager.addOrder("ORDER004", 80.0);
customerManager.updateNotificationPreferences(true, true, false);
System.out.println("Account Balance: $" + customerManager.getAccountBalance());
System.out.println("Loyalty Points: " + customerManager.getLoyaltyPoints());
}
}Output:
Personal info updated for customer: CUST002
Address updated for customer: CUST002
Transaction logged: $750.0
Email sent: Account balance updated
Transaction logged: $200.0
Email sent: Payment processed successfully
Email sent: Order ORDER003 confirmed
Email sent: Order ORDER004 confirmed
Notification preferences updated
Account Balance: $550.0
Loyalty Points: 20Benefits of Extract Class
Benefit | Description |
|---|---|
Single Responsibility | Each class now has a single, well-defined responsibility, making them easier to understand and maintain. |
Improved Testability | Smaller, focused classes are easier to test in isolation with fewer dependencies and edge cases. |
Better Reusability | Extracted classes can be reused in other contexts without bringing unnecessary dependencies. |
When to Apply Extract Class Refactoring
When classes have too many fields and methods (typically more than 20-30 methods).
When classes have multiple reasons to change (violating Single Responsibility Principle).
When classes contain groups of related methods that could be logically separated.
When classes are difficult to understand due to their size and complexity.
When classes have low cohesion where methods don't work together toward a common purpose.
Apply Extract Class refactoring to one large class in your current project today. Start with the class that has the most obvious distinct responsibilities.
Repository & Resources
Complete Code Examples: Clean Code Repository
Find the complete implementation of Large Class refactoring and other clean code techniques in our dedicated repository. Each example includes:
Before and after code comparisons
Unit tests demonstrating the improvements
Found this helpful? Share it with a colleague who's struggling with Large Classes. Got questions? We'd love to hear from you at [email protected]