VO(Value Object)란 말 그대로 값 객체라는 의미를 가지고 있다. 즉, 데이터를 담는 객체이다.
VO의 핵심 역할은 equals()와 hashcode()를 오버라이딩 하는 것이다. 즉, VO 내부에 선언된 속성(필드)의 모든 값들이 VO 객체마다 값이 같아야 같은 객체라고 판별한다.
Value Object에는 기본 특성이 있다.
- Immutability(불변성) - 수정자(setter)가 없음
- Value Equality(값 동등성) - 내부 값 동등성 검사
- Self Validation(자가 유효성 검사) - 생성자에서 validate
Value Object는 불변이다.
불변이라는 뜻은 한번 생성되면 이후 내부 값을 바꿀 수 없음을 의미한다. 즉 (수정자)Setter를 허용하지 않는다는 말이다.
쉽게 말하면, 생성자에게 하나 혹은 그 이상의 파라미터가 주입돼서 객체가 만들어지면, 돌아갈 수 없음을 의미한다. 해당 객체는 GC(Garbage Collector)에 의해 폐기될 때까지 동일함을 보장한다.
Value Object를 조작하는 방법
- 생성자 또는 정적 메소드를 통해 새 인스턴스 만들기
- 현재 객체에서 다른 객체 생성하기
- 내부 데이터를 추출해 다른 유형으로 변환하기
- 정적 팩토리 메소드 패턴을 활용해 생성하기(생성자를 private하게 바꿈)
위와 같은 방법은 Value Object를 명확하게 해준다.
final class ComplexNumber {
private float realPart;
private float complexPart;
// 정적 메소드를 통해 새 인스턴스 만들기
public static ComplexNumber zero() {
return new ComplexNumber(0, 0);
}
// 생성자를 통해 새 인스턴스를 만들기
private ComplexNumber(float realPart, float complexPart) {
this.realPart = realPart;
this.complexPart = complexPart;
}
// 정적 팩토리 메소드 패턴을 활용하여 생성하기 (생성자를 private하게 바꿈)
public static ComplexNumber of(float realPart, float complexPart) {
return new ComplexNumber(realPart, complexPart);
}
// 현재 객체에서 다른 객체를 생성하기
public ComplexNumber add(ComplexNumber anotherComplexNumber) {
return new ComplexNumber(
realPart + anotherComplexNumber.realPart,
complexPart + anotherComplexNumber.complexPart
);
}
// 내부 데이터를 추출하여 다른 유형으로 변환하기
public String toString() {
return String.format("%f + %f i", realPart, complexPart);
}
}
Value Object는 내부 값이 모둗 각각 동일한지 확인하여 동등성을 테스트할 수 있다.
동일성 vs 동등성
동일성은 객체가 완전히 같은지 판단할 수 있는 성질이고, 동등성은 객체가 같은 정보를 가지고 있는지 판단할 수 있는 성질이다.
동일성은==
로 주소값을 비교하고, 동등성은equals()
로 내용을 비교한다.
Value Object는 context에서 유효한 값만 허용한다. 이는 유효하지 않은 값으로 Value Object를 만들 수 없음을 의미한다.
생성자가 주입될 때 값의 유효성을 확인해야 한다. 유효하지 않으면 의미있는 에러를 표출한다. 이는 객체의 인스턴스에 더이상 if가 없음을 의미한다. 모든 유효성 검사는 생성 시간에 이루어진다.
객체를 Value Object로 만들기 위한 체크 리스트