• 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:

  1. Identify data clumps that appear together in multiple places.

  2. Create new classes to represent these data groups.

  3. Move related data into the new classes.

  4. Update method signatures to use the new objects.

  5. Move related behaviour into the new classes.

Here's the refactored version:

The scattered data clumps have been organized into meaningful classes:

  1. PatientManager - Simplified service class

  2. PatientDemographics - Extracted demographic data

  3. ContactInformation - Extracted contact details

  4. InsuranceDetails - Extracted insurance data

  5. Appointment - Extracted appointment information

  6. 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 successfully

Benefits 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 getFullName() or getFormattedCoverage(), making the code more expressive.

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]