최근 받은 코드 리뷰에서 VO에 대한 필요성을 느끼고 직접 공부하고 적용하는 과정을 담았습니다.
VO(Value Object)는 무엇일까?
번역하면 '값 객체'이다. 이는 고유 식별자가 없고 변경 불가능한 특성을 가진 객체를 말한다. 객체 자체가 아니라 값이 중요한 경우에 사용된다.
VO는 다음과 같은 특성을 지니고 있다.
- 불변성(Immutability): 한 번 생성되면 상태는 변경될 수 없다.
- 동등성(Equality): 식별자와 관계 없이, 같은 값(value)을 갖는다면 같은 것으로 간주한다.
- 캡슐화(Encapsulation): 도메인 개념을 캡슐화하고 특정한 값에만 집중하도록 분리함으로써 복잡성을 줄인다.
- 유효성(Validation): VO가 표현하는 값은 항상 도메인 규칙에 따라 유효하도록 보장한다.
이러한 특성에 따라, VO는 주로 이메일 주소, 전화번호, 날짜, 금액 등에 사용된다.
VO(Value Object)는 왜 쓰는걸까?
만약 예시처럼 모든 필드를 String으로 정의했을 때, 각 필드에 대한 개념 간 구분이 불명확합니다. 또한, 모두가 같은 자료형을 사용하고 있으므로 엉뚱한 값을 전달할 가능성이 있습니다. 필드에 대해 유효성 검증이나 비즈니스 로직이 필요할 경우, Host 클래스 내에서 모두 수행된다면 객체의 책임과 역할을 명확하게 정의하지 못한 것이 됩니다.
하지만 VO를 사용하면 필드를 다음과 같은 형태로 표현할 수 있습니다.
public final class Host {
private final HostName name;
private final HostManager hostManager;
}
public final class HostManager {
private final ManagerName name;
private final Email email;
private final PhoneNumber phoneNumber;
}
이를 통해 객체 간 속성을 분리하여 관리한다면, 각 필드가 어떤 의미를 갖는지 명확히 파악할 수 있으며
필드마다 필요한 유효성 검증이나 비즈니스 로직 또한 해당 클래스에서 관리하기 용이해지며 책임과 역할을 분리함으로써, 객체지향적인 설계와 활용이 가능합니다.
객체 동등성 (equals와 hashCode 정의하기)
객체 동등성 규칙을 따르기 위해서는 eqauals와 hashCode를 같이 정의해야한다. 이는 HashMap, HashSet 혹은 다른 컬렉션에서 제대로 동작하게하기 위함이다.
해시 기반 컬렉션은 hashCode를 먼저 사용하여 객체를 찾고 이후 equals를 사용하여 실제 동등성을 확인한다.
따라서, VO에서 같은 value에 대해 hashCode 메서드를 실행한 결과가 같아야한다.
하지만 기존 Object 클래스 내 메서드를 사용할 경우, 이를 구현할 수 없다.
Object 클래스 hashCode, equals 메서드
hashCode 메서드는 객체의 메모리 주소값을 기준으로 hashCode 값을 반환하므로
이를 Override하여 메서드를 재정의해야한다.
equals 메서드 역시 arg의 obj와의 메모리 주소값을 기준으로 동등성을 비교하므로 VO에서 활용할 수 없다.
Override를 통해 hashCode, equals 메서드 재정의하기
Email 클래스를 VO로 설정하고 hashCode와 eqauals 메서드를 재정의 해보자.
package com.example.event.domain.value;
import com.example.event.exception.value.EmailCreateException;
import java.util.Objects;
import java.util.regex.Pattern;
public final class Email {
private static final String EMAIL_REGEX = "^[A-Za-z0-9+_.-]+@(.+)$";
private static final Pattern PATTERN = Pattern.compile(EMAIL_REGEX);
private final String address;
}
hashCode 메서드
@Override
public int hashCode() {
return Objects.hash(address);
}
객체의 메모리 주소값이 아닌 address(String)에 대한 해시값을 반환함
equals 메서드
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Email email = (Email) o;
return Objects.equals(email.address, address);
}
객체의 메모리 주소를 포함해 클래스 동일성 여부, 속성 비교를 통해 추가 판별
테스트 코드
출처
'Java' 카테고리의 다른 글
객체지향의 사실과 오해를 읽고 (1) | 2025.02.12 |
---|---|
자바의 런타임 데이터 영역에 대하여 (2) | 2024.06.15 |
자바의 실행과정과 JIT에 대하여 (0) | 2024.06.12 |
추상클래스와 인터페이스에 대하여 (0) | 2024.06.12 |
JAVA를 잡아보자 (프로그래밍 언어, 자바) (2) | 2024.01.21 |