# Primitive ObsessionA intermediate-level guide to Primitive Obsession: before-and-after java code and diagrams for a CS student.

## The Illusion of Simplicity

Imagine you are building a user registration system. At first, it seems easy: you just need a few strings for names and an integer for age. You pass these primitive types into a method, and everything works perfectly. 

However, as the application grows, you realize that "Email" is not just any string—it must contain an "@" symbol. "Age" is not just any number—it cannot be negative or over 150. Suddenly, your business rules are leaking into every single method that touches these variables. You find yourself writing the same `if` statements over and over again to check if a string looks like an email or if an integer is within a valid range.

This leads to "fragile" code where one mistake—like accidentally swapping two string arguments in a method call—causes a silent failure or a runtime crash because the compiler sees nothing wrong with two strings being passed in the wrong order.

```java
public class UserService {
    // Primitives are used for domain concepts, leading to validation leakage
    public void createUser(String firstName, String lastName, String email, int age) {
        if (email == null || !email.contains("@")) {
            throw new IllegalArgumentException("Invalid email");
        }
        if (age < 0 || age > 150) {
            throw new IllegalArgumentException("Invalid age");
        }
        System.out.println("User created: " + firstName + " " + lastName);
    }

    public void sendWelcomeEmail(String email) {
        // Validation logic is duplicated everywhere the primitive is used
        if (email == null || !email.contains("@")) {
            throw new IllegalArgumentException("Invalid email");
        }
        System.out.println("Sending mail to " + email);
    }
}
```

```mermaid
classDiagram
    class UserService {
        +createUser(String, String, String, int) void
        +sendWelcomeEmail(String) void
    }
```

## Naming the Pain: Primitive Obsession

The problem you are seeing is a code smell called **Primitive Obsession**. This occurs when developers use basic data types (primitives like `int`, `double`, or `String`) to represent complex domain concepts. 

When we do this, we lose the ability to let the type system protect us. We rely on "validation leakage," where the logic required to ensure a piece of data is valid is scattered throughout the entire codebase instead of being held in one secure place. If the definition of a "valid email" changes, you have to hunt through every class that uses an email string to update your `if` checks.

```mermaid
flowchart TD
    A[UserService] -->|uses String for email| B(Invalid Data?)
    B -- yes --> C[Throw Exception]
    A -->|uses int for age| D(Invalid Data?)
    D -- yes --> E[Throw Exception]
    style B fill:#f96,stroke:#333
    style D fill:#f96,stroke:#333
```

## The Refactor: From Primitives to Value Objects

To fix this, we use a refactoring technique called **Replace Primitive with Object** (often implemented via the **Introduce Parameter Object** pattern). Instead of passing raw strings and integers, we create small, dedicated classes that represent our domain concepts. 

In Domain-Driven Design (DDD), these are known as **Value Objects**. A Value Object is an object whose identity is defined by its data rather than a unique ID. Most importantly, a Value Object validates itself during construction. If you have a `EmailAddress` object in your hand, you can trust it is valid without checking it again.

```java
public record EmailAddress(String value) {
    public EmailAddress { 
        if (value == null || !value.contains("@")) {
            throw new IllegalArgumentException("Invalid email format");
        }
    }
}

public record UserAge(int value) {
    public UserAge { 
        if (value < 0 || value > 150) {
            throw new IllegalArgumentException("Age must be between 0 and 150");
        }
    }
}

public class UserService {
    // Using Value Objects ensures the parameters are always valid
    public void createUser(String firstName, String lastName, EmailAddress email, UserAge age) {
        System.out.println("User created: " + firstName + " " + lastName);
    }

    public void sendWelcomeEmail(EmailAddress email) {
        // No validation needed here; the type itself guarantees correctness
        System.out.println("Sending mail to " + email.value());
    }
}
```

```mermaid
classDiagram
    class UserService {
        +createUser(String, String, EmailAddress, UserAge) void
        +sendWelcomeEmail(EmailAddress) void
    }
    class EmailAddress {
        +value: String
    }
    class UserAge {
        +value: int
    }
    UserService ..> EmailAddress : uses
    UserService ..> UserAge : uses
```

In this new structure, the `UserService` no longer cares *how* an email is validated; it only cares that it has received a valid `EmailAddress` object. This enforces the Single Responsibility Principle (SRP) by moving validation logic out of the service and into the data types themselves.

```mermaid
sequenceDiagram
    participant US as UserService
    participant EA as EmailAddress
    US ->> EA: new EmailAddress("test@example.com")
    EA -->> US: valid object
    US ->> US: createUser(...)
```

## When to Use It, and When Not To

While Value Objects provide massive safety gains, they are not a silver bullet. 

You should reach for this pattern when a primitive has specific rules or logic associated with it that repeats in multiple places. If you find yourself writing `if (string.contains("@"))` in more than one class, it is time to create an object.

However, avoid over-engineering by creating Value Objects for everything. If you are simply moving data across a network in a Data Transfer Object (DTO), or if a value has no rules associated with it and will never be reused, sticking to primitives can keep your code from becoming unnecessarily verbose. Do not mistake "more classes" for "better architecture"; use objects when they provide meaningful protection.

## Takeaways

- Primitive Obsession occurs when basic types are used to represent complex domain concepts.
- Refactor by replacing primitives with **Value Objects** that validate themselves upon creation.
- A Value Object guarantees its own validity, preventing validation leakage across your services.
- Use these objects to make your code self-describing and harder to break via simple argument swaps.

## Usage

**Using the refactored API with Value Objects**

```java
UserService service = new UserService();
service.createUser("Jane", "Doe", new EmailAddress("jane@example.com"), new UserAge(30));
```
