# Value Object Pattern

## Problem

Primitive types (strings, numbers) are frequently used to represent domain concepts like money, email addresses, or coordinates. But primitives carry no validation, no behavior, and no semantic meaning — `$10 USD` is just a float, and mixing currencies or comparing incompatible values causes bugs that the type system can't catch.

## Solution

Create small, **immutable** objects that represent domain concepts by **value** rather than identity. Two value objects are equal if all their attributes are equal (unlike entities, which are equal by ID). Value objects encapsulate validation and domain-specific behavior.

Key properties:
- **Immutable** — Once created, never changed.
- **Equality by value** — Two `Money(10, "USD")` instances are equal.
- **Self-validating** — Invalid states are rejected at construction.
- **Side-effect free** — Operations return new instances.

## When to Use

- Money/currency amounts
- Email addresses, phone numbers, URLs
- Geographic coordinates, measurements
- Date ranges, time periods
- Anywhere primitives carry implicit constraints

## When to Avoid

- When identity matters (use Entity instead)
- When mutability is genuinely required
- When the concept is truly a single primitive with no validation needs

## Pseudocode

```
class Money:
    amount: decimal (immutable)
    currency: string (immutable)

    function equals(other):
        return amount == other.amount AND currency == other.currency

    function add(other):
        assert currency == other.currency
        return new Money(amount + other.amount, currency)
```

## Python Implementation

```python
from dataclasses import dataclass


@dataclass(frozen=True)
class Money:
    amount: float
    currency: str

    def __post_init__(self):
        if self.amount < 0:
            raise ValueError(f"Amount cannot be negative: {self.amount}")
        if len(self.currency) != 3:
            raise ValueError(f"Currency must be 3-letter code: {self.currency}")

    def add(self, other: "Money") -> "Money":
        if self.currency != other.currency:
            raise ValueError(f"Cannot add {self.currency} and {other.currency}")
        return Money(round(self.amount + other.amount, 2), self.currency)

    def subtract(self, other: "Money") -> "Money":
        if self.currency != other.currency:
            raise ValueError(f"Cannot subtract {self.currency} and {other.currency}")
        result = round(self.amount - other.amount, 2)
        if result < 0:
            raise ValueError("Result would be negative")
        return Money(result, self.currency)

    def multiply(self, factor: float) -> "Money":
        return Money(round(self.amount * factor, 2), self.currency)

    def __str__(self):
        return f"{self.currency} {self.amount:.2f}"


@dataclass(frozen=True)
class Email:
    address: str

    def __post_init__(self):
        if "@" not in self.address or "." not in self.address.split("@")[-1]:
            raise ValueError(f"Invalid email: {self.address}")
        # Normalize to lowercase
        object.__setattr__(self, "address", self.address.lower())

    def domain(self) -> str:
        return self.address.split("@")[1]

    def __str__(self):
        return self.address


# --- Demo ---
print("=== Money value object ===")
price = Money(29.99, "USD")
tax = Money(2.40, "USD")
total = price.add(tax)
print(f"  {price} + {tax} = {total}")

discounted = total.multiply(0.9)
print(f"  10% discount: {discounted}")

# Equality by value
a = Money(10.00, "USD")
b = Money(10.00, "USD")
c = Money(10.00, "EUR")
print(f"  Money(10, USD) == Money(10, USD)? {a == b}")
print(f"  Money(10, USD) == Money(10, EUR)? {a == c}")

# Can be used in sets/dicts (hashable because frozen)
prices = {Money(9.99, "USD"), Money(9.99, "USD"), Money(19.99, "USD")}
print(f"  Unique prices: {len(prices)}")

print()
print("=== Email value object ===")
email = Email("Alice@Example.COM")
print(f"  Normalized: {email}")
print(f"  Domain: {email.domain()}")
print(f"  Equal to lowercase? {email == Email('alice@example.com')}")

print()
print("=== Validation ===")
try:
    Money(-5, "USD")
except ValueError as e:
    print(f"  Negative amount: {e}")

try:
    Money(10, "USD").add(Money(5, "EUR"))
except ValueError as e:
    print(f"  Mixed currency: {e}")

try:
    Email("not-an-email")
except ValueError as e:
    print(f"  Bad email: {e}")
```

**Output:**
```
=== Money value object ===
  USD 29.99 + USD 2.40 = USD 32.39
  10% discount: USD 29.15
  Money(10, USD) == Money(10, USD)? True
  Money(10, USD) == Money(10, EUR)? False
  Unique prices: 2

=== Email value object ===
  Normalized: alice@example.com
  Domain: example.com
  Equal to lowercase? True

=== Validation ===
  Negative amount: Amount cannot be negative: -5
  Mixed currency: Cannot add USD and EUR
  Bad email: Invalid email: not-an-email
```

## Go Implementation

```go
package main

import (
	"fmt"
	"math"
	"strings"
)

type Money struct {
	Amount   float64
	Currency string
}

func NewMoney(amount float64, currency string) (Money, error) {
	if amount < 0 {
		return Money{}, fmt.Errorf("amount cannot be negative: %f", amount)
	}
	if len(currency) != 3 {
		return Money{}, fmt.Errorf("currency must be 3-letter code: %s", currency)
	}
	return Money{Amount: math.Round(amount*100) / 100, Currency: currency}, nil
}

func (m Money) Add(other Money) (Money, error) {
	if m.Currency != other.Currency {
		return Money{}, fmt.Errorf("cannot add %s and %s", m.Currency, other.Currency)
	}
	return NewMoney(m.Amount+other.Amount, m.Currency)
}

func (m Money) Multiply(factor float64) Money {
	result, _ := NewMoney(m.Amount*factor, m.Currency)
	return result
}

func (m Money) Equal(other Money) bool {
	return m.Amount == other.Amount && m.Currency == other.Currency
}

func (m Money) String() string {
	return fmt.Sprintf("%s %.2f", m.Currency, m.Amount)
}

type Email struct {
	Address string
}

func NewEmail(address string) (Email, error) {
	lower := strings.ToLower(address)
	parts := strings.SplitN(lower, "@", 2)
	if len(parts) != 2 || !strings.Contains(parts[1], ".") {
		return Email{}, fmt.Errorf("invalid email: %s", address)
	}
	return Email{Address: lower}, nil
}

func main() {
	fmt.Println("=== Money value object ===")
	price, _ := NewMoney(29.99, "USD")
	tax, _ := NewMoney(2.40, "USD")
	total, _ := price.Add(tax)
	fmt.Printf("  %s + %s = %s\n", price, tax, total)

	discounted := total.Multiply(0.9)
	fmt.Printf("  10%% discount: %s\n", discounted)

	a, _ := NewMoney(10.00, "USD")
	b, _ := NewMoney(10.00, "USD")
	fmt.Printf("  Equal? %v\n", a.Equal(b))

	fmt.Println()
	fmt.Println("=== Email value object ===")
	email, _ := NewEmail("Alice@Example.COM")
	fmt.Printf("  Normalized: %s\n", email.Address)

	fmt.Println()
	fmt.Println("=== Validation ===")
	_, err := NewMoney(-5, "USD")
	fmt.Printf("  Negative: %v\n", err)

	_, err = NewEmail("not-an-email")
	fmt.Printf("  Bad email: %v\n", err)
}
```

## JavaScript Implementation

```javascript
class Money {
  #amount;
  #currency;

  constructor(amount, currency) {
    if (amount < 0) throw new Error(`Amount cannot be negative: ${amount}`);
    if (currency.length !== 3) throw new Error(`Currency must be 3-letter code: ${currency}`);
    this.#amount = Math.round(amount * 100) / 100;
    this.#currency = currency;
    Object.freeze(this);
  }

  get amount() { return this.#amount; }
  get currency() { return this.#currency; }

  add(other) {
    if (this.#currency !== other.currency) {
      throw new Error(`Cannot add ${this.#currency} and ${other.currency}`);
    }
    return new Money(this.#amount + other.amount, this.#currency);
  }

  multiply(factor) {
    return new Money(this.#amount * factor, this.#currency);
  }

  equals(other) {
    return this.#amount === other.amount && this.#currency === other.currency;
  }

  toString() {
    return `${this.#currency} ${this.#amount.toFixed(2)}`;
  }
}

// --- Demo ---
console.log("=== Money value object ===");
const price = new Money(29.99, "USD");
const tax = new Money(2.40, "USD");
const total = price.add(tax);
console.log(`  ${price} + ${tax} = ${total}`);
console.log(`  10% discount: ${total.multiply(0.9)}`);

const a = new Money(10, "USD");
const b = new Money(10, "USD");
console.log(`  Equal? ${a.equals(b)}`);

console.log("\n=== Validation ===");
try { new Money(-5, "USD"); } catch (e) { console.log(`  Negative: ${e.message}`); }
try { new Money(10, "USD").add(new Money(5, "EUR")); } catch (e) { console.log(`  Mixed: ${e.message}`); }
```

## Related Patterns

- **Immutability** — Value Objects must be immutable. Immutability is the key property that makes Value Objects safe to share.
- **Flyweight** — When the same Value Object instance is shared across many places, Flyweight manages that sharing efficiently.
- **Guard Clause** — Value Object constructors use Guard Clause to validate and reject invalid values at creation time.
- **Aggregate** — Value Objects live inside Aggregates (as properties of entities). They are part of the Aggregate but are not entities.
- **DTO** — A DTO transfers data; a Value Object models a domain concept. A Value Object can be a field type inside a DTO.
