- Shift Elevate
- Posts
- Data Clumps: Extract Class Refactoring | Clean Code
Data Clumps: Extract Class Refactoring | Clean Code
Data Clumps are groups of data that always appear together but are not organized into a single object.
We will see how we can refactor using the Extract Class technique to group related data and reduce parameter lists.
Clean Code Reference
⚠️ Code Smell: Data Clumps
✅ Refactoring: Extract Class
🎯 Goal: Group related data and reduce parameter coupling
The Data Clumps code smell occurs when the same group of data items appears together in multiple places throughout the code, but they are not organized into a single object. The Extract Class refactoring technique groups these related data items into a meaningful object, reducing parameter lists and improving code organization.
The Code Smell: Data Clumps
Data Clumps are groups of data that always appear together but are scattered across multiple parameters, method signatures, and classes. This creates tight coupling between methods and makes the code harder to maintain. When you need to add or modify related data, you have to change multiple places in the code.
Symptoms | Impact |
|---|---|
Same group of parameters in multiple methods | Tight coupling |
Related data scattered across code | Difficult to maintain |
Long parameter lists | Poor code organization |
Here's a typical example of Data Clumps:
public class PatientManager {
public void registerPatient(String firstName, String lastName,
String phone, String email,
String insuranceProvider, String policyNumber) {
Patient patient = new Patient();
patient.setFirstName(firstName);
patient.setLastName(lastName);
patient.setPhone(phone);
patient.setEmail(email);
patient.setInsuranceProvider(insuranceProvider);
patient.setPolicyNumber(policyNumber);
savePatient(patient);
}
public void updateContactInfo(String firstName, String lastName,
String newPhone, String newEmail) {
Patient patient = findPatient(firstName, lastName);
patient.setPhone(newPhone);
patient.setEmail(newEmail);
savePatient(patient);
}
public void scheduleAppointment(String firstName, String lastName,
String phone, String email,
String appointmentDate, String appointmentType) {
String message = String.format("Appointment scheduled for %s %s on %s for %s",
firstName, lastName, appointmentDate, appointmentType);
sendEmailConfirmation(email, "Appointment Confirmation", message);
sendSmsConfirmation(phone, message);
}
private Patient findPatient(String firstName, String lastName) {
return new Patient();
}
private void savePatient(Patient patient) { }
private void sendEmailConfirmation(String email, String subject, String message) { }
private void sendSmsConfirmation(String phone, String message) { }
}
public class Patient {
private String firstName;
private String lastName;
private String phone;
private String email;
private String insuranceProvider;
private String policyNumber;
public String getFirstName() { return firstName; }
public void setFirstName(String firstName) { this.firstName = firstName; }
public String getLastName() { return lastName; }
public void setLastName(String lastName) { this.lastName = lastName; }
public String getPhone() { return phone; }
public void setPhone(String phone) { this.phone = phone; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getInsuranceProvider() { return insuranceProvider; }
public void setInsuranceProvider(String insuranceProvider) { this.insuranceProvider = insuranceProvider; }
public String getPolicyNumber() { return policyNumber; }
public void setPolicyNumber(String policyNumber) { this.policyNumber = policyNumber; }
}In this example, we can see data clumps everywhere: patient demographics (firstName, lastName), contact information (phone, email), insurance details (provider, policyNumber), and appointment information (appointmentDate, appointmentType) appear together in multiple methods. This creates long parameter lists and makes the code hard to maintain.
The Refactoring: Extract Class
The Extract Class refactoring technique groups related data items into meaningful objects, reducing parameter lists and improving code organization. This makes the code more maintainable and reduces coupling between methods.
By creating dedicated classes for patient demographics, contact information, insurance details, and appointments, we transform the scattered primitive parameters into cohesive objects. Each extracted class encapsulates related data and provides meaningful methods, making the code more organized and easier to understand.
Step by Step Refactoring Process:
Identify data clumps that appear together in multiple places.
Create new classes to represent these data groups.
Move related data into the new classes.
Update method signatures to use the new objects.
Move related behaviour into the new classes.
Here's the refactored version:
The scattered data clumps have been organized into meaningful classes:
PatientManager - Simplified service class
PatientDemographics - Extracted demographic data
ContactInformation - Extracted contact details
InsuranceDetails - Extracted insurance data
Appointment - Extracted appointment information
Patient - Coordinator class
PatientManager - Simplified Service Class
public class PatientManager {
public void registerPatient(PatientDemographics demographics,
ContactInformation contactInfo,
InsuranceDetails insurance) {
Patient patient = new Patient(demographics, contactInfo, insurance);
savePatient(patient);
}
public void updateContactInfo(PatientDemographics demographics,
ContactInformation newContactInfo) {
Patient patient = findPatient(demographics);
patient.updateContactInfo(newContactInfo);
savePatient(patient);
}
public void scheduleAppointment(PatientDemographics demographics,
ContactInformation contactInfo,
Appointment appointment) {
String message = String.format("Appointment scheduled for %s on %s for %s",
demographics.getFullName(), appointment.getDate(), appointment.getType());
contactInfo.sendEmailConfirmation("Appointment Confirmation", message);
contactInfo.sendSmsConfirmation(message);
}
private Patient findPatient(PatientDemographics demographics) {
return new Patient();
}
private void savePatient(Patient patient) { }
}PatientDemographics - Extracted Demographic Data
public class PatientDemographics {
private final String firstName;
private final String lastName;
public PatientDemographics(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public String getFullName() {
return firstName + " " + lastName;
}
public String getFirstName() { return firstName; }
public String getLastName() { return lastName; }
@Override
public String toString() {
return String.format("Patient{name='%s %s'}", firstName, lastName);
}
}
ContactInformation - Extracted Contact Details
public class ContactInformation {
private final String phone;
private final String email;
public ContactInformation(String phone, String email) {
this.phone = phone;
this.email = email;
}
public void sendEmailConfirmation(String subject, String message) {
System.out.println("Email sent to " + email + ": " + subject);
}
public void sendSmsConfirmation(String message) {
System.out.println("SMS sent to " + phone + ": " + message);
}
public String getPhone() { return phone; }
public String getEmail() { return email; }
@Override
public String toString() {
return String.format("Contact{phone='%s', email='%s'}", phone, email);
}
}public class ContactInformation {
private final String phone;
private final String email;
public ContactInformation(String phone, String email) {
this.phone = phone;
this.email = email;
}
public void sendEmailConfirmation(String subject, String message) {
System.out.println("Email sent to " + email + ": " + subject);
}
public void sendSmsConfirmation(String message) {
System.out.println("SMS sent to " + phone + ": " + message);
}
public String getPhone() { return phone; }
public String getEmail() { return email; }
@Override
public String toString() {
return String.format("Contact{phone='%s', email='%s'}", phone, email);
}
}InsuranceDetails - Extracted Insurance Data
public class InsuranceDetails {
private final String provider;
private final String policyNumber;
public InsuranceDetails(String provider, String policyNumber) {
this.provider = provider;
this.policyNumber = policyNumber;
}
public String getFormattedCoverage() {
return String.format("%s (Policy: %s)", provider, policyNumber);
}
public String getProvider() { return provider; }
public String getPolicyNumber() { return policyNumber; }
@Override
public String toString() {
return getFormattedCoverage();
}
}
public class InsuranceDetails {
private final String provider;
private final String policyNumber;
public InsuranceDetails(String provider, String policyNumber) {
this.provider = provider;
this.policyNumber = policyNumber;
}
public String getFormattedCoverage() {
return String.format("%s (Policy: %s)", provider, policyNumber);
}
public String getProvider() { return provider; }
public String getPolicyNumber() { return policyNumber; }
@Override
public String toString() {
return getFormattedCoverage();
}
}Appointment - Extracted Appointment Information
public class Appointment {
private final String date;
private final String type;
public Appointment(String date, String type) {
this.date = date;
this.type = type;
}
public String getDate() { return date; }
public String getType() { return type; }
@Override
public String toString() {
return String.format("Appointment{date='%s', type='%s'}", date, type);
}
}public class Appointment {
private final String date;
private final String type;
public Appointment(String date, String type) {
this.date = date;
this.type = type;
}
public String getDate() { return date; }
public String getType() { return type; }
@Override
public String toString() {
return String.format("Appointment{date='%s', type='%s'}", date, type);
}
}Patient - Coordinator Class
public class Patient {
private final PatientDemographics demographics;
private ContactInformation contactInfo;
private final InsuranceDetails insurance;
public Patient(PatientDemographics demographics,
ContactInformation contactInfo,
InsuranceDetails insurance) {
this.demographics = demographics;
this.contactInfo = contactInfo;
this.insurance = insurance;
}
public void updateContactInfo(ContactInformation newContactInfo) {
this.contactInfo = newContactInfo;
}
public PatientDemographics getDemographics() { return demographics; }
public ContactInformation getContactInfo() { return contactInfo; }
public InsuranceDetails getInsurance() { return insurance; }
@Override
public String toString() {
return String.format("Patient{demographics=%s, contact=%s, insurance=%s}",
demographics, contactInfo, insurance);
}
}public class Patient {
private final PatientDemographics demographics;
private ContactInformation contactInfo;
private final InsuranceDetails insurance;
public Patient(PatientDemographics demographics,
ContactInformation contactInfo,
InsuranceDetails insurance) {
this.demographics = demographics;
this.contactInfo = contactInfo;
this.insurance = insurance;
}
public void updateContactInfo(ContactInformation newContactInfo) {
this.contactInfo = newContactInfo;
}
public PatientDemographics getDemographics() { return demographics; }
public ContactInformation getContactInfo() { return contactInfo; }
public InsuranceDetails getInsurance() { return insurance; }
@Override
public String toString() {
return String.format("Patient{demographics=%s, contact=%s, insurance=%s}",
demographics, contactInfo, insurance);
}
}Usage Example - Putting It All Together
Notice how the refactored version transforms 8 primitive parameters (firstName, lastName, phone, email, insuranceProvider, policyNumber, appointmentDate, appointmentType) into just 4 meaningful objects. The PatientDemographics object encapsulates demographic data, ContactInformation handles contact details, InsuranceDetails manages insurance data, and Appointment represents appointment information. This not only reduces parameter counts but also makes the code self-documenting.
When you call demographics.getFullName() or insurance.getFormattedCoverage(), it's immediately clear what you're working with. Compare this to the original scattered parameters where they could easily be confused or passed in the wrong order.
public class PatientRegistrationDemo {
public static void main(String[] args) {
System.out.println("=== Patient Registration System ===\n");
PatientDemographics demographics = new PatientDemographics("John", "Doe");
ContactInformation contactInfo = new ContactInformation("555-123-4567", "[email protected]");
InsuranceDetails insurance = new InsuranceDetails("Blue Cross Blue Shield", "BCBS123456");
PatientManager patientManager = new PatientManager();
patientManager.registerPatient(demographics, contactInfo, insurance);
System.out.println("\nPatient registered successfully:");
System.out.println(" Name: " + demographics.getFullName());
System.out.println(" Email: " + contactInfo.getEmail());
System.out.println(" Insurance: " + insurance.getFormattedCoverage());
System.out.println("\n=== Scheduling Appointment ===");
Appointment appointment = new Appointment("2024-03-15 10:00 AM", "Annual Checkup");
patientManager.scheduleAppointment(demographics, contactInfo, appointment);
System.out.println("\n=== Updating Contact Information ===");
ContactInformation updatedContact = new ContactInformation("555-111-2222", "[email protected]");
patientManager.updateContactInfo(demographics, updatedContact);
System.out.println("Contact information updated successfully");
}
}Output:
=== Patient Registration System ===
Patient registered successfully:
Name: John Doe
Email: [email protected]
Insurance: Blue Cross Blue Shield (Policy: BCBS123456)
=== Scheduling Appointment ===
Email sent to [email protected]: Appointment Confirmation
SMS sent to 555-123-4567: Appointment scheduled for John Doe on 2024-03-15 10:00 AM for Annual Checkup
=== Updating Contact Information ===
Contact information updated successfullyBenefits of Extract Class for Data Clumps
Benefit | Description |
|---|---|
Reduced Parameter Lists | Methods have fewer parameters, making them easier to call and understand. |
Better Code Organization | Related data is grouped together, making the code more logical and maintainable. |
Reduced Coupling | Changes to related data only require updates in one place, reducing the impact of changes. |
Meaningful Methods | Each object provides domain-specific methods like |
When to Apply Extract Class for Data Clumps
The same group of parameters appears in multiple method signatures.
Related data items are scattered across multiple classes.
Long parameter lists that are hard to understand and maintain.
When you find yourself passing the same set of parameters to multiple methods.
When related data changes together and should be treated as a unit.
Apply Extract Class refactoring to one set of data clumps in your current project today. Start with the data clumps that appear most frequently across your codebase.
Repository & Resources
Complete Code Examples: Clean Code Repository
Find the complete implementation of Data Clumps 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 Data Clumps. Got questions? We'd love to hear from you at [email protected]