본 포스트 시리즈는 「도메인 주도 설계 철저 입문」책을 요약한 내용입니다.
이전 발행 글 보기
02장 시스템 특유의 값을 나타내기 위한 '값 객체'
(1) 값 객체란?
값 객체
값 객체란 시스템 특유의 값을 나타내는 객체를 말한다. 다르게 말하면 시스템에서 필요로하는 값이 원시 데이터 타입이 아닌 경우에 필요한 것이 바로 '값 객체'이다.
예를 들어 성명을 원시 데이터 타입인 String 타입으로 나타내면, 성과 이름을 표기하는 규칙이 '성-이름'순 혹은 '이름-성' 순으로 다를 때 프로그램에서 성명을 적절하게 다루기 어렵다. 따라서 이때 FullName이라는 클래스를 생성하는데, 이것이 바로 값 객체이다.
public class FullName {
// 필드
private String firstName;
private String lastName;
// 생성자
public FullName(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
// 이름 반환 메서드
public String getFirstName() {
return firstName;
}
// 성 반환 메서드
public String getLastName() {
return lastName;
}
// 전체 이름 반환 메서드
public String getFullName() {
return firstName + " " + lastName;
}
}
값의 성질 (1) - 불변성과 교환 가능성
값은 변하지 않는 성질을 갖는다. 이는 변수에 새로운 값을 대입하는 것과 다른 이야기이다. 변수 lastName에 "김"이 할당되어있었는데 여기에 "이"를 집어넣는다고 해서 "김"이라는 값 자체가 "이"로 바뀌는 것은 아니기 때문이다. 단지 "이"라는 값을 새로 생성해서 "김" 대신 교환한 것 뿐이다. 즉, 값은 교환 가능한 성질을 가지고 있다.
lastName = "김";
lastName = "이";
값의 성질 (2) - 등가성 비교 가능
동일한 종류의 값끼리는 비교연산자 '==' 또는 'equals' 함수를 통해 비교가 가능하다. 값 객체도 결국 하나의 값이기 때문에, 객체끼리 비교할 수 있도록 equals 함수를 오버라이딩하여 비교 메서드를 제공하는 것이 바람직하다.
public class FullName {
// 필드
private String firstName;
private String lastName;
// 생성자
public FullName(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
// 객체 비교 메서드
@Override
public boolean equals(Object obj) {
// 같은 객체를 비교하는 경우
if (this == obj) return true;
// 클래스 타입이 다르거나 null인 경우
if (obj == null || getClass() != obj.getClass()) return false;
// 다운캐스팅
FullName fullName = (FullName) obj;
// 이름과 성이 모두 동일한지 비교
return firstName.equals(fullName.firstName) && lastName.equals(fullName.lastName);
}
(생략)
}
값 객체가 객체간 비교 수단을 제공하는 것이 바람직한 이유는, 값 객체에 가령 middleName과 같은 새로운 속성이 추가 되었을 때를 생각해보면 확실히 알 수 있다.
만약 equals 메서드 없이 프로그램 전역에서 firstName과 lastName을 일일히 꺼내 비교했다면, 그 코드를 찾아 모두 수정해주어야 할 것이다. 하지만 equals를 사용하는 경우에는 equals 메서드만 수정을하면 되기 때문에 효율적이다.
어디까지 값 객체로 만들 것인가
위에서는 FullName 클래스의 필드인 firstName과 lastName을 원시 타입인 String으로 구현하였지만, 필요한 경우엔 다음과 같이 FirstName, LastName으로 필드마저 클래스로 구현이 가능하다.
public class FullName {
// 필드
private FirstName firstName;
private LastName lastName;
// 생성자
public FullName(FirstName firstName, LastName lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
(생략)
}
public class FirstName {
// 필드
private String firstName;
// 생성자
public FirstName(String firstName) {
if (firstName.isEmpty() || firstName == null) throw new Exception("최소 한 글자 이상이어야 함.");
this.firstName = firstName;
}
(생략)
}
public class LastName {
// 필드
private String lastName;
// 생성자
public LastName(String lastName) {
if (lastName.isEmpty() || lastName == null) throw new Exception("최소 한 글자 이상이어야 함.");
this.lastName = lastName;
}
(생략)
}
타당한 이유가 있다면 과감하게 값 객체로 만드는 것이 좋다. 참고로 위 코드와 같이 이름에 대해 강제할 규칙(최소 한 글자 이상이라는 규칙) 있다는 것은 충분한 이유가 될 수 없다. FirstName, LastName 클래스를 만들지 않아도 FullName 클래스의 생성자에서 그 규칙을 정의할 수 있기 때문이다.
값 객체는 행동을 가질 수 있다
값 객체는 데이터 뿐만이 아니라 행동도 가질 수 있다. 다음 Money라는 값 객체는 덧셈이라는 행동을 가진다. 반면, 값 객체에 multiply라는 행동은 정의되어있지 않으므로, 암묵적으로 Money라는 값 객체는 곱셈은 가능하지 않음을 알 수 있다.
public class Money {
// 필드: 금액과 화폐 단위
private double amount;
private String currency;
// 생성자
public Money(double amount, String currency) {
this.amount = amount;
this.currency = currency;
}
// 금액 반환 메서드
public double getAmount() {
return amount;
}
// 화폐 단위 반환 메서드
public String getCurrency() {
return currency;
}
// add 메서드: 같은 화폐 단위를 가진 두 Money 객체의 금액을 더함
public Money add(Money other) {
if (!this.currency.equals(other.currency)) {
throw new IllegalArgumentException("Different currencies: " + this.currency + " and " + other.currency);
}
return new Money(this.amount + other.getAmount(), this.currency);
}
}
요약
1. 값 객체란 시스템에서 필요로 하는 개념을 클래스로 만든 것이다. 2. 값은 다음과 같은 성질을 지닌다. 1) 불변성 2) 교환 가능성 3) 같은지 비교 가능성 3. 타당한 이유가 있는 한 아주 사소해 보이는 것까지도 값 객체로 만드는 것이 좋다. 4. 값 객체는 행동도 가질 수 있다. |
다음 글
2024.09.05 - [DDD/도메인 주도 설계 철저 입문] - 도메인 주도 설계란? - 「도메인 주도 설계 철저 입문」 2장 (2)
'DDD > 도메인 주도 설계 철저 입문' 카테고리의 다른 글
도메인 서비스란? - 「도메인 주도 설계 철저 입문」 4장 (2) | 2024.09.30 |
---|---|
엔티티의 생애주기와 도메인 객체 - 「도메인 주도 설계 철저 입문」 3장 (2) (0) | 2024.09.11 |
도메인 엔티티란? - 「도메인 주도 설계 철저 입문」 3장 (1) (1) | 2024.09.09 |
값 객체의 장점 - 「도메인 주도 설계 철저 입문」 2장 (2) (2) | 2024.09.05 |
도메인 주도 설계란? - 「도메인 주도 설계 철저 입문」 1장 (7) | 2024.09.02 |