본문 바로가기

Java

VO(Value Object)

VO는 Entity, DTO, 도메인 주도 설계 등과 함께 언급되는 개념 중 하나임.

VO(Value Object)

When programming, I often find it's useful to represent things as a compound.
**👉 프로그래밍할 때, 사물을 복합물로 표현하는 것이 유용한 경우가 종종 있다.**

A 2D coordinate consists of an x value and y value. An amount of money consists of a number and a currency. A date range consists of start and end dates, which themselves can be compounds of year, month, and day.
**👉 예를 들면 x, y로 이루어진 2차원 좌표를 표현하거나, 숫자와 통화로 이루어진 금액, 시작 날짜와 끝 날짜로 이루어진 날짜 기간 등이 있다.**

VO란 도메인에서 한 개 또는 그 이상의 속성들을 묶어 특정 값을 나타내는 객체를 의미.

  • 도메인 객체의 일종
  • 해당 속성들은 primitive 타입임!(int, boolean, …)
  • 기본 키를 식별 값으로 갖는 Entity와는 구분해서 사용.

VO를 쓰는 이유?

  • primitive 타입이 도메인 객체를 모델링하기 충분하지 않기 떄문.
    • 2차원 좌표가 대표적인 예가 될 수 있을듯.

1. primitive 타입의 기능들을 객체가 전부 사용하지 않음.

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

위와 같은 코드가 있다고 가정하자.

Point에서 x, y는 원시 타입인 int 형으로 선언되어 있음.

그치만 x, y는 int형의 모든 기능을 사용하지 않고, 사용할 필요도 없음.

2. 여러 곳에서 사용 시 중복코드 발생

x, y는 현재 Point라는 객체에서 사용되고 있다.

그치만 Point가 아닌 만약 또 다른 객체에서 x, y를 사용한다면 어떨까?

그리고 x, y에 대해 동일한 조건의 검사가 필요하다면 유효성 검사 코드가 중복될 것이다.

3. 여러 곳에서 사용 시 불변 보장 가능성

x, y가 여러 곳에서 사용될때 각 객체에서의 x, y가 불변임을 확인하려면 어떻게 해야할까?

모든 객체에 일일이 들어가 final 키워드가 붙었는지 확인해야 한다.

VO로 생성한다면 해당 VO 코드에서만 확인하면 됨.

VO는 어떤 조건으로 Entity와 구분될까?

1. equals & hash code 메서드 재정의

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

@Test
void equals() {
  Point point = new Point(2, 3);
  Point point2 = new Point(2, 3);
  
  // point != point2
  assertThat(point == point2).isFalse();  // 동일성 비교
}

point, point2는 타입도 같고, 속성값도 같은 두 객체이지만 동일성 비교 시 다른 객체로 구분되는 것을 확인할 수 있다.

같은 위치를 가리키지만 다른 위치라고 판단하는 데

이 문제를 해결하기 위해서는 동일성 비교동등성 비교의 차이를 알아야 함,

  • 동일성(==) 비교객체가 참조하는 주소값을 확인함.
    • point와 point2가 참조하는 메모리 주소값은 서로 다름.
  • 동등성 비교
    • 객체가 포함하고 있는 속성값을 기준으로 객체를 비교함.
    • equals **메서드 재정의(Overriding)**를 통해 가능
    • 어떤 속성값을 기준으로 동등성 비교를 할 것인지 직접 정의해야 함.
// equals & hashcode 재정의
...
    @Override
    public boolean equals(final Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        final Point point = (Point) o;
        return x == point.x &&
                y == point.y;
    }

    @Override
    public int hashCode() {
        return Objects.hash(x, y);
    }
...

위 예제 코드에서는 hashCode()도 함께 재정의함.

hashCode() - 객체를 식별할 하나의 정수값을 가리키고, 기본 사용 시 메모리 주소값을 기준으로 해시 값을 만듦.

재정의를 통해 특정 값을 기준으로 같은 해시값을 만들 수 있음.

→ 해시값을 사용하는(컬렉션 등) 곳에서 객체를 비교하는 용도로 사용.

equals, hashCode 재정의 통해 VO 사용 시 속성 값이 같은 객체는 동일 객체임 보장 가능.

2. 수정자(setter)가 없는 불변 객체

  • Entity는 식별 값을 따로 갖기 때문에 내부 속성 값이 변경되더라도 같은 객체로 인식되어 추적할 수 있음.
  • VO는 속성 값 자체가 식별 값이므로 값 변경 시 추적이 불가.⇒ VO는 반드시 값을 변경할 수 없는 불변 객체로 만들어야 함!!
  • 복사될 경우 객체들이 함께 변경되는 문제를 유발.
// Order.java
public class Order {
  private String restaurant;
  private String food;
  private int quantity;
  
  **// Getter, Setter ...**
  // equals & hashcode
}

// Main.java
public static void main(String[] args) {

  Order 첫번째주문 = new Order();
  첫번째주문.setRestaurant("황제떡볶이");
  첫번째주문.setFood("매운떡볶이");
  첫번째주문.setQuantity(2);
  // 첫번째주문 = {restaurant='황제떡볶이', food='매운떡볶이', quantity=2}

  Order 두번째주문 = 첫번째주문;
  // 두번째주문 = {restaurant='황제떡볶이', food='매운떡볶이', quantity=2}

  두번째주문.setFood("안매운떡볶이");  //** 주문 변경
  두번째주문.setQuantity(3);       //** 주문 변경
  // 첫번째주문 = {restaurant='황제떡볶이', food='안매운떡볶이', quantity=3}
  // 두번째주문 = {restaurant='황제떡볶이', food='안매운떡볶이', quantity=3}
  
}

위 예제를 보면 두 번째 주문은 첫 번째 주문 객체를 복사함.

이때 두 번째 주문이 변경되면 첫 번째 주문도 변경됨을 알 수 있음.

왜냐면 두 번째 주문은 값을 복사한 것이 아니라 메모리 주소가 복사된 것이기 때문임!!!

이 문제를 해결하기 위해 VO중간에 값이 변할 수 없도록 해야 함.

**수정자(setter)**를 없애야 한다.

<aside> ❓

수정자가 없으면 VO 값은 어떻게 설정하지?

</aside>

  • 생성자를 이용.
    • 객체 생성 시 값이 한 번만 할당되고 이후에 변경되지 않도록.
  • 생성자를 통해 불변 객체로 만들자!
public class Order {
  private String restaurant;
  private String food;
  private int quantity;
  
  public Order(String restaurant, String food, int quantity) {
    this.restaurant = restaurant;
    this.food = food;
    this.quantity = quantity;
  }
  
  // only getter.. **(setter 없음!!!!)**
}

public static void main(String[] args) {

  Order 첫번째주문 = new Order("황제떡볶이", "매운떡볶이", 2);
  // 첫번째주문 = {restaurant='황제떡볶이', food='매운떡볶이', quantity=2}

  Order 두번째주문 = new Order("황제떡볶이", "매운떡볶이", 2)
  // 두번째주문 = {restaurant='황제떡볶이', food='매운떡볶이', quantity=2}

  두번째주문 = new Order("황제떡볶이", "안매운떡볶이", 3)  //** 주문 변경
  // 첫번째주문 = {restaurant='황제떡볶이', food='매운떡볶이', quantity=2}
  // 두번째주문 = {restaurant='황제떡볶이', food='안매운떡볶이', quantity=3}
  
}

setter가 없기 때문에 속성 값의 변화가 생긴다면 객체를 새로 생성해야 함.

  • 이를 통해 VO의 정체성(속성 값 자체가 식별자 역할)을 지킬 수 있음.
  • 의도치 않은 변경을 막아 유지보수에 도움을 줌.

VO 사용 시 이점

VO를 왜 사용할까? 원시 타입만으로도 충분하다고 생각하는데??

  • VO를 통해 Domain 설계 → 객체 생성 시 제약사항 추가.
  • 생성될 인스턴스가 정해져 있는 경우 → 캐싱을 통해 성능 향상 가능성 존재.
  • Entity의 원사 값을 VO로 포장 → Entity가 거대해지는 것을 예방할 수 있음.

Reference

VO(Value Ojbect)란 무엇일까?

 

VO(Value Ojbect)란 무엇일까?

프로그래밍을 하다 보면 VO라는 이야기를 종종 듣게 된다. VO와 함께 언급되는 개념으로는 Entity, DTO등이 있다. 그리고 더 나아가서는 도메인 주도 설계까지도 함께 언급된다. 이 글에서는 우선 다

tecoble.techcourse.co.kr

VO(Value Object)는 무엇일까? 왜 사용할까?

 

VO(Value Object)는 무엇일까? 왜 사용할까?

1. VO(Value Object)란? VO의 의미를 보면 다음과 같다. * VO란 도메인에서 한 개 또는 그 이상의 속성들을 묶어서 특정 값을 나타내는 객체를 의미한다. * 해당 속성들을 primitive 타입이다! (int, boolean, ...

ksh-coding.tistory.com