- Shift Elevate
- Posts
- Primitive Obsession: Replace Primitive with Object Refactoring | Clean Code
Primitive Obsession: Replace Primitive with Object Refactoring | Clean Code
Primitive Obsession is a code smell where primitive types are used instead of small objects to represent domain concepts.
We will see how we can refactor using the Replace Primitive with Object technique for better domain modeling and type safety.
⚠️ Code Smell: Primitive Obsession
✅ Refactoring: Replace Primitive with Object
🎯 Goal: Rich domain models with meaningful types
The Primitive Obsession code smell occurs when primitive types (strings, numbers, booleans) are used to represent domain concepts instead of creating dedicated objects. The Replace Primitive with Object refactoring technique replaces these primitives with meaningful objects that better represent the domain and provide type safety.
The Code Smell: Primitive Obsession
Primitive Obsession is a subtle but important code smell that reduces the expressiveness of your domain model. When you use primitive types to represent domain concepts, you lose the opportunity to encapsulate related behaviour and make your code more self-documenting. This leads to scattered validation logic and makes the code harder to understand and maintain.
Symptoms | Impact |
|---|---|
Primitive types used for domain concepts | Reduced expressiveness |
Scattered validation logic | Poor domain modeling |
Type confusion and errors | Higher bug risk |
Here's a typical example of Primitive Obsession:
public class User {
private String email;
private String phoneNumber;
private String zipCode;
private double salary;
private String status;
public User(String email, String phoneNumber, String zipCode,
double salary, String status) {
this.email = email;
this.phoneNumber = phoneNumber;
this.zipCode = zipCode;
this.salary = salary;
this.status = status;
}
public boolean isValidEmail() {
return email != null && email.contains("@") && email.contains(".");
}
public boolean isValidPhoneNumber() {
return phoneNumber != null && phoneNumber.matches("\\+?[1-9]\\d{1,14}");
}
public boolean isValidZipCode() {
return zipCode != null && zipCode.matches("\\d{5}(-\\d{4})?");
}
public boolean isActive() {
return "ACTIVE".equals(status);
}
}In this example, the User class uses primitive types like String for email, phone number, zip code, and status, and double for salary. This creates several problems: validation logic is scattered, there's no type safety, and the domain concepts are not clearly expressed.
The Refactoring: Replace Primitive with Object
The Replace Primitive with Object refactoring technique creates dedicated objects to represent domain concepts, encapsulating related behaviour and providing type safety. This makes the code more expressive and easier to maintain.
Step by Step Refactoring Process:
Identify primitive types that represent domain concepts.
Create dedicated classes for each domain concept.
Move validation logic into the new classes.
Replace primitive fields with the new objects.
Update methods to work with the new objects.
Here's the refactored version:
// User class now uses meaningful objects
public class User {
private Email email;
private PhoneNumber phoneNumber;
private ZipCode zipCode;
private Money salary;
private UserStatus status;
public User(Email email, PhoneNumber phoneNumber, ZipCode zipCode,
Money salary, UserStatus status) {
this.email = email;
this.phoneNumber = phoneNumber;
this.zipCode = zipCode;
this.salary = salary;
this.status = status;
}
public boolean isActive() {
return status.isActive();
}
}
// Dedicated class for email with validation
public class Email {
private final String value;
public Email(String email) {
if (!isValid(email)) {
throw new IllegalArgumentException("Invalid email format: " + email);
}
this.value = email;
}
private boolean isValid(String email) {
return email != null && email.contains("@") && email.contains(".");
}
public String getValue() {
return value;
}
public String getDomain() {
return value.substring(value.indexOf("@") + 1);
}
}
// Dedicated class for phone numbers
public class PhoneNumber {
private final String value;
public PhoneNumber(String phoneNumber) {
if (!isValid(phoneNumber)) {
throw new IllegalArgumentException("Invalid phone number format: " + phoneNumber);
}
this.value = phoneNumber;
}
private boolean isValid(String phoneNumber) {
return phoneNumber != null && phoneNumber.matches("\\+?[1-9]\\d{1,14}");
}
public String getValue() {
return value;
}
public String getCountryCode() {
if (value.startsWith("+")) {
return value.substring(1, 3);
}
return "US"; // Default assumption
}
}
// Dedicated class for zip codes
public class ZipCode {
private final String value;
public ZipCode(String zipCode) {
if (!isValid(zipCode)) {
throw new IllegalArgumentException("Invalid zip code format: " + zipCode);
}
this.value = zipCode;
}
private boolean isValid(String zipCode) {
return zipCode != null && zipCode.matches("\\d{5}(-\\d{4})?");
}
public String getValue() {
return value;
}
}
// Dedicated class for money
public class Money {
private final double amount;
public Money(double amount) {
if (amount < 0) {
throw new IllegalArgumentException("Amount cannot be negative");
}
this.amount = amount;
}
public double getAmount() {
return amount;
}
public String getFormattedAmount() {
return String.format("$%.2f", amount);
}
}
// Enum for user status
public enum UserStatus {
ACTIVE, INACTIVE, SUSPENDED, PENDING;
public boolean isActive() {
return this == ACTIVE;
}
}Benefits of Replace Primitive with Object
Benefit | Description |
|---|---|
Rich Domain Model | Objects represent domain concepts clearly, making the code more expressive and self-documenting. |
Type Safety | Compile-time type checking prevents errors and makes the code more robust. |
Encapsulated behaviour | Related validation and business logic is encapsulated within the appropriate objects. |
When to Apply Replace Primitive with Object Refactoring
Primitive types used to represent domain concepts (email, phone, money, etc.).
Scattered validation logic for the same primitive type.
Multiple parameters of the same primitive type that could be confused.
Business logic that operates on primitive values.
When you want to make your domain model more expressive and type-safe.
Repository & Resources
Complete Code Examples: Clean Code Repository
Find the complete implementation of Primitive Obsession 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 Primitive Obsession. Got questions? We'd love to hear from you at [email protected]