VO는 Entity, DTO, 도메인 주도 설계 등과 함께 언급되는 개념 중 하나임.
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란 도메인에서 한 개 또는 그 이상의 속성들을 묶어 특정 값을 나타내는 객체를 의미.
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형의 모든 기능을 사용하지 않고, 사용할 필요도 없음.
x, y는 현재 Point라는 객체에서 사용되고 있다.
그치만 Point가 아닌 만약 또 다른 객체에서 x, y를 사용한다면 어떨까?
그리고 x, y에 대해 동일한 조건의 검사가 필요하다면 유효성 검사 코드가 중복될 것이다.
x, y가 여러 곳에서 사용될때 각 객체에서의 x, y가 불변임을 확인하려면 어떻게 해야할까?
모든 객체에 일일이 들어가 final 키워드가 붙었는지 확인해야 한다.
VO로 생성한다면 해당 VO 코드에서만 확인하면 됨.
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 & 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 사용 시 속성 값이 같은 객체는 동일 객체임 보장 가능.
// 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)
를 없애야 한다.
수정자가 없으면 VO 값은 어떻게 설정하지?
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를 왜 사용할까? 원시 타입만으로도 충분하다고 생각하는데??