Value Object,
- 값이 곧 정체성인 객체. 값이 같으면 같은 것이고, 한번 만들면 바꿀 수 없다.
- 값 자체가 의미인 객체
풀어서 말하면,
평소에 코드 짜다보면 이런거 씀
String email = "hong@gmail.com";
int price = 10000;
String zipCode = "06134";
이게 그냥 문자열, 숫자로 떠돌아다니다 보면 두 가지 문제가 발생
문제 1 — 검증 코드가 여기저기 흩어짐
// A 파일에서
if (email.contains("@")) { ... }
// B 파일에서도
if (email.contains("@") && email.length() > 5) { ... }
문제 2 — 타입이 같아서 실수해도 모름
void join(String email, String phone) { ... }
join("010-1234-5678", "hong@gmail.com"); // 바꿔 넣어도 오류 없음
VO가 이 두 문제를 해결하는 방식
// 검증 + 관련 기능을 객체 안에 캡슐화
public class Email {
private final String value; // 불변
public Email(String value) {
if (!value.contains("@")) throw new Exception("이메일 형식 오류");
this.value = value; // 여기서 한 번만 검증
}
}
public class PhoneNumber {
private final String value;
public PhoneNumber(String value) {
if (!value.startsWith("010")) throw new Exception("전화번호 형식 오류");
this.value = value;
}
}
// 이제 순서 바꾸면 컴파일 타임에 바로 잡힘
void join(Email email, PhoneNumber phone) { ... }
VO로 뭘 할 수 있냐?
1. 검증을 한 곳에 모은다. -> 생성자에서 한 번만 검증하면, 이 객체를 받는 쪽은 이미 유효한 값임을 믿을 수 있음
2. 타입으로 실수를 막는다. -> String 대신 email, PhoneNumber 타입을 쓰면 잘못된 값이 들어오는 걸 컴파일 타임에 차단
언제 쓰냐?
- 이 값에 대해 검증 로직이 있다
- 같은 타입인데 의미가 다른게 있다
반대로
고유한 ID가 필요하다 -> Entity
한 줄 요약
: 원시 타입 (String, int) 으로 떠돌던 값에 이름과 책임을 부여한 것 : VO
타입 안정성 + 검증 증집 + 관련 기능 캡슐화, 이 세가지를 얻기 위해 씀
쉽게 설명하면,
핵심 비유 : 1만원 짜리 지폐
지갑에서 1만원짜리 지폐 두 장을 꺼냈다고 생각
두 지폐의 일련번호는 다르지만, 우리는 둘 다 똑같이 1만원으로 취급함
카페에서 커피를 살 때 이 지페가 아니면 안돼 ! 라고 하지 않음
=> 값이 같으면 = 같은 것 으로 보는게 VO
반대로, Entity 는 ?
같은 비유로 사람을 생각해보면,
"홍길동" 이라는 이름을 가진 사람이 둘 있어도, 둘은 다른 사람임.
이름 (값)이 같아도 각자의 고유한 정체성 (ID)가 있으니까
지폐 (VO) -> 값이 같으면 같은 것
사람 (entity) -> ID(주민번호) 가 다르면 다른 것
VO의 또 다른 특징 - "불변"
1만원 짜리 지폐는 그 자체로 완성된 값임
1만원을 2만원으로 수정하는 건 불가능. 새 지폐를 만들어야함
VO 도 똑같음. 한번 만들어진 VO는 값을 바꿀 수 없고 새로운 VO를 만들어야함
기존 1만원을 2만원으로 수정 XXX
기존 1만원 + 새 1만원 = 2만원 짜리 새 객체 생성 O
반면 주문, 회원, 상품은 내용이 같아도 다를 수 있으니 Entity임
같은 상품이 두개 있어도, 재고 번호가 다르면 다른 상품이니까
왜 쓰는가?
1. 문제상황
회원가입기능 만들기
회원가입(이름, 이메일, 전화번호)
그런데 개발자가 실수로 순서를 바꿔서 호출함
회원가입("홍길동", "010-1234-5678", "hong@gmail.com")
↑ 이메일 자리에 ↑ 전화번호 자리에
전화번호 입력 이메일 입력
둘 다 그냥 문자열 (String) 이라서, 컴퓨터는 아무 오류도 안냄
나중에 문자 발송 할 때 되서야 발견하게 됨
VO를 쓰면 ?
이메일은 Email 타입, 전화번호는 PhoneNumber 타입으로 만들어두면
회원가입("홍길동", 전화번호, 이메일)
↑
"Email 자리에 PhoneNumber가 들어왔어요!" 즉시 오류 발생
코드를 실행하기 전에 실수를 잡아냄
실제 쇼핑몰에서 사용하는 구현 예시
쇼핑몰에 등장하는 VO 후보들
주문 흐름: 회원가입 → 상품 검색 → 장바구니 → 결제 → 배송
↓ ↓ ↓ ↓ ↓
Email Price Money CardNumber Address
PhoneNumber
1. 회원가입 - Email, PhoneNumber
문제상황
// ❌ VO 없이 - 잘못 입력해도 오류 안 남
User 회원가입("홍길동", "010-1234-5678", "hong@gmail.com");
// ↑ 이메일 자리 ↑ 전화번호 자리
// 전화번호 입력 이메일 입력 → 오류 없음!
VO 적용 후
// Email VO
public class Email {
private final String value;
public Email(String value) {
if (!value.contains("@"))
throw new Exception("이메일 형식이 아닙니다"); // 생성 시 바로 검증
this.value = value;
}
}
// PhoneNumber VO
public class PhoneNumber {
private final String value;
public PhoneNumber(String value) {
if (!value.startsWith("010"))
throw new Exception("전화번호 형식이 아닙니다"); // 생성 시 바로 검증
this.value = value;
}
}
// ✅ VO 적용 - 순서 바꾸면 즉시 오류
User 회원가입("홍길동", new Email("hong@gmail.com"), new PhoneNumber("010-1234-5678"));
핵심 정리
VO는 "관련된 것들을 한 곳에 모아서, 실수를 줄이고 수정을 쉽게 " 만드는 도구