VO란?

vo는 값 객체입니다. 이름 그대로 값을 표현하는 객체로, 객체의 속성이 곧 객체의 정체성이 됩니다. 따라서 객체의 속성이 변경되면 속성이 바뀌기 전과 동일한 객체로 취급할 수 없게 됩니다.

예를 들어 500원 동전 객체를 생성하고 500원 동전의 속성이 400원으로 변경되었을 때, 이 동전은 더이상 500원으로서의 역할을 하지 못합니다. 500원이라는 값 자체로 의미를 갖는 객체였기 때문에 객체의 속성이 달라진다면 동일한 객체로 취급할 수 없기 때문입니다. 따라서 자연스럽게 vo는 불변이 보장되어야 합니다. 속성이 바뀌는 것은 객체가 바뀌는 것과 마찬가지이기 때문입니다.

같은 맥락에서 값 자체로 의미를 갖는 객체이기 때문에 같은 속성값을 갖는다면 속성에 대한 동등성을 부여해 같은 객체로 취급할 수 있어야 합니다. 이러한 특성을 지키기 위해 vo객체들은 equals & hashCode를 재정의해주어야 합니다.

VO의 예시

다음과 같은 primitive 타입의 price 가 있습니다.

int price = 1000;

위의 원시 타입을 아래와 같은 클래스로 wrapping할 수 있습니다. 위에서 언급한 것처럼 vo객체를 사용할 때는 반드시 equals & hashCode를 재정의해주어야 합니다.

public class Price {

    private final int price;

    public Price(int price) {
    this.price = price;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Price that = (Price) o;
        return Objects.equals(price, that.price);
    }

    @Override
    public int hashCode() {
        return Objects.hash(price);
    }
}

VO의 필요성

vo 객체를 사용해본 경험이 있으신가요?

지금까지 vo 객체에 대해 알고 있었고, 원시값을 포장해야 하는 이유들에 대해 공부했지만 vo 객체를 사용했을 때의 단점이 vo 객체를 사용했을 때의 이점보다 크게 느껴져 실제로 코드에 적용해본 경험이 없었습니다.

vo 객체를 사용했을 때의 가장 큰 단점은 getter() 사용이 불가피하다는 점이라고 생각했습니다. 객체지향적인 코드를 위해 getter() 사용을 지양했었는데, vo 객체를 사용하면 결국 어딘가에서는 getter() 를 통해 원시값을 비교해야 합니다. 특히 웹 애플리케이션에서 이처럼 원시값을 포장하는 경우 dto를 만드는 과정에서 다음과 같이 디미터의 법칙을 지키지 못하는 코드가 되어버립니다.

(디미터의 법칙: 사용하는 객체의 내부를 몰라야 한다. 여러개의 ‘.’을 사용하지 않는다.)

public OrderResponse toOrderResponse(final OrderEntity orderEntity) {
    return new OrderResponse(
            orderEntity.getName(),
            orderEntity.getPrice().getPrice(),
            orderEntity.getPoint().getPoint()
    );
}

그러나 vo 객체를 사용하면 위의 클린코드를 지키지 못한다는 단점을 상쇄할만한 이점들이 많이 있습니다.

  1. 리팩토링의 비용을 줄일 수 있다.

vo를 사용한다면 원시값의 타입이 변경되는 경우 리팩토링의 비용을 줄일 수 있었습니다.

예를 들어 Product 객체의 속성으로 int price 를 갖는다고 가정해봅시다. 그리고 product 객체의 price 속성은 상품을 구입하는 로직, 장바구니에 담는 로직, 구입 목록의 총 금액을 계산하는 로직 등에서 사용된다고 가정한다면, price 의 속성이  double price 등으로 변경되었을 때 위에서 price 가 사용되는 모든 로직을 변경해주어야 합니다. 그러나 price 가 vo객체였다면 vo의 필드 타입만 변경해주면 됩니다. 지금까지 미션을 진행해본 크루들이라면 타입을 변경했을 때 뜨는 20 related problems 와 같은 문구가 주는 스트레스를 잘 알 것이라고 생각합니다.

비즈니스 로직과 도메인 요구사항은 언제든지 변할 수 있고, 요구사항이 변경되었을 때 변경되는 코드를 최소화 할 수 있다면 다른 비즈니스 로직에 더욱 집중할 수 있을 것입니다. 이처럼 리팩토링의 비용을 줄일 수 있다는 것만으로도 충분히 vo를 사용할 가치가 있습니다.