1. 상수 필드나 열거형의 한계

  • 상수 필드: 단순한 값(변하지 않는 데이터)을 표현하기에 적합.
    public class Constants {
        public static final String ROLE_ADMIN = "ADMIN";
        public static final String ROLE_USER = "USER";
    }
    
    • 이런 상수는 값 자체를 표현하지만, 값 간의 연산이나 동작을 정의할 수 없음.
    • 불변성은 보장되지만, 상수 값이 가지는 의미나 동작을 캡슐화하기 어렵다.
  • 열거형(Enum): 고정된 값 집합을 표현할 때 적합.
    public enum Role {
        ADMIN, USER, GUEST;
    }
    
    • 열거형은 제한된 값 집합을 표현할 수는 있지만, 복잡한 연산이나 추가 데이터 처리에는 한계가 있음.
    • 동작을 포함할 수 있으나, 주로 값을 열거하기 위한 용도에 사용.


2. VO를 사용하는 이유

VO는 데이터와 그 데이터를 다루는 로직을 함께 묶어서 표현하기 때문에 다음과 같은 장점이 있습니다:

1) 데이터의 캡슐화

  • 단순한 값 외에도, 값 간의 관계나 연산 로직을 포함할 수 있음.
  • 상수나 열거형은 정적인 값만 제공하지만, VO는 값을 다루는 비즈니스 로직을 추가할 수 있음.
// 금액(Money) 표현
public final class Money {
    private final BigDecimal amount;

    public Money(BigDecimal amount) {
        if (amount.compareTo(BigDecimal.ZERO) < 0) {
            throw new IllegalArgumentException("금액은 0 이상이어야 합니다.");
        }
        this.amount = amount;
    }

    public Money add(Money other) {
        return new Money(this.amount.add(other.amount));
    }

    public Money subtract(Money other) {
        return new Money(this.amount.subtract(other.amount));
    }

    public BigDecimal getAmount() {
        return amount;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Money money = (Money) o;
        return amount.equals(money.amount);
    }

    @Override
    public int hashCode() {
        return Objects.hash(amount);
    }
}
  • Money VO는 금액을 표현하면서 동시에 금액을 더하거나 뺄 수 있는 로직을 캡슐화.
  • 상수나 열거형으로는 금액의 연산이나 유효성 검사를 포함할 수 없음.

2) 불변성 보장

  • VO는 불변 객체로 설계되기 때문에 값 변경에 대한 걱정 없이 사용 가능.
  • 상수 필드나 열거형은 단순히 값을 읽는 용도로 사용되지만, VO는 상태를 포함하지 않고도 복잡한 데이터 표현이 가능.

3) 의미 있는 표현

  • VO는 단순히 값을 나열하는 것이 아니라, 값 자체가 가지는 의미를 명확히 드러냄.
  • 예를 들어, 좌표(Point)나 거리(Distance)를 표현하는 VO는 그 데이터의 목적을 명확히 드러냄.
// 좌표(Point) 표현
public final class Point {
    private final int x;
    private final int y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public double distanceTo(Point other) {
        int dx = this.x - other.x;
        int dy = this.y - other.y;
        return Math.sqrt(dx * dx + dy * dy);
    }

    @Override
    public String toString() {
        return "Point{" + "x=" + x + ", y=" + y + '}';
    }
}
  • 상수 필드나 열거형으로는 두 좌표 간 거리 계산 같은 동작을 포함할 수 없음.

4) 값의 동등성 보장

  • VO는 equals()hashCode()를 재정의하여 값의 동등성을 비교할 수 있음.
  • 상수 필드는 참조를 비교하지만, VO는 값 자체를 비교.

예: 두 VO 객체의 값 비교

Point p1 = new Point(3, 4);
Point p2 = new Point(3, 4);

System.out.println(p1.equals(p2)); // true, 값이 동일하므로 동등


3. VO와 상수/열거형의 선택 기준

특성 VO 상수 필드/열거형
복잡한 데이터 표현 가능 (값 간 연산, 검증 로직 포함 가능) 불가능 (정적 값만 표현 가능)
값의 불변성 보장 보장 보장
값 간의 관계 정의 가능 불가능
동작 포함 가능 (메서드 추가 가능) 제한적 (주로 정적 값만 정의)
사용 예 금액, 좌표, 거리, 주소와 같은 복잡한 데이터 처리 사용자 권한(ADMIN, USER), 고정된 코드 값 등.


4. 결론

  • VO는 값을 표현하면서 관련된 로직까지 포함할 수 있는 객체로, 단순히 불변성만 제공하는 상수나 열거형보다 더 유연하고 의미 있는 데이터 표현이 가능합니다.
  • 상수 필드/열거형은 단순한 정적 값이나 고정된 집합(예: 역할, 상태)에서 적합하며, VO는 비즈니스 로직이나 값 간 관계가 필요한 경우에 사용됩니다.