PS. 셋은 엄밀히 말하면 다른 용어입니다. 제가 알아본 바에 따르면 VO의 불변성에 의해 Setter를 쓰지 않는 것이 원칙이지만, 실무에서 간혹 VO(값 객체)에 Setter를 사용하기도 하는 것을 확인했습니다. 그리고 그렇게 했을 때 사실상 클래명만 VO로 하는 경우도 많아 프로그램이 동작하는 데에도 차이가 없었습니다.
- Object : 엔터티(데이터베이스에서의 용어), 객체(프로그래밍에서의 용어)
- VO(Value Object) : 값 객체, 불변 객체
- 불변성(Immutable) : Setter와 같은 가변 로직이 없는 불변 상태여야 한다.
- 동등성(Equality) : VO 객체의 주소 값이 다르더라도 값이 같다면 동등한 객체로 판단한다.
- 자가 유효성 검사(Self-Validation)
// VO (Value Object) - Money
public class MoneyVO {
private final double amount; // 금액
private final String currency; // 통화
// 생성자
public MoneyVO(double amount, String currency) {
if (amount < 0) {
throw new IllegalArgumentException("Amount cannot be negative");
}
this.amount = amount;
this.currency = currency;
}
// 금액을 반환하는 메서드
public double getAmount() {
return amount;
}
// 통화를 반환하는 메서드
public String getCurrency() {
return currency;
}
// 동등성 비교: 값 기반으로 비교
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
MoneyVO money = (MoneyVO) obj;
return Double.compare(moneyVO.amount, amount) == 0 && currency.equals(moneyVO.currency);
}
// hashCode() 메서드: equals()를 재정의하면 hashCode()도 재정의해야 한다.
@Override
public int hashCode() {
return Objects.hash(amount, currency);
}
}
- DTO : 데이터 전송 객체
// DTO (Data Transfer Object) - UserDTO
public class UserDTO {
private String name; // 사용자 이름
private int age; // 사용자 나이
private String email; // 사용자 이메일
// 기본 생성자
public UserDTO() {}
// 생성자
public UserDTO(String name, int age, String email) {
this.name = name;
this.age = age;
this.email = email;
}
// getter/setter 메서드
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
VO (Value Object) | DTO (Data Transfer Object) | |
목적 | 비즈니스 로직에서 중요한 값을 나타내는 객체 | 데이터 전송을 목적으로 하는 객체 |
불변성 | 불변 (값이 변경되지 않음) | 변경 가능 (값을 수정할 수 있음) |
동등성 비교 | 값 기준으로 동등성 비교 | 보통 식별자 기준으로 동등성 비교 |
식별자 | 식별자 없음 (값만 중요) | 식별자(ID)가 포함될 수 있음 |
사용 용도 | 도메인 모델에서 값을 표현하는 데 사용 | 계층 간 데이터 전달(네트워크 전송, API 응답 등) |
예시 | Money, Address, Email 등 | UserDTO, ProductDTO, OrderDTO 등 |
VO는 어떻게 사용할까 : Ojbect에서 VO 사용하기
//Object(객체=클래스=엔티티)
public class Order {
private final String orderId;
private final MoneyVO totalAmount; // VO(값 객체)를 사용하여 총 금액을 나타냄
// 생성자
public Order(String orderId, Money totalAmount) {
this.orderId = orderId;
this.totalAmount = totalAmount;
}
// getter 메서드
public String getOrderId() {
return orderId;
}
public MoneyVO getTotalAmount() {
return totalAmount;
}
// 주문 금액을 추가하는 메서드
public Order addAmount(Money additionalAmount) {
Money newTotal = this.totalAmount.add(additionalAmount);
return new Order(this.orderId, newTotal); // 새로운 Order 객체 생성 (불변성)
}
// 주문 정보 출력
public void printOrderDetails() {
System.out.println("Order ID: " + orderId);
System.out.println("Total Amount: " + totalAmount.getAmount() + " " + totalAmount.getCurrency());
}
}
Ojbect를 VO로 바꿀 수 있을까?
- 객체를 값 객체로 변경하는 것이 가능할까?
- 드물지만 불가능하지는 않습니다.
- 객체를 값 객체로 변경하는 것은 불가능하거나 적합하지 않은 경우가 많습니다. 객체는 식별자, 상태 변화, 동등성 비교에서 식별자 기준을 사용하는 반면, VO는 값 기반으로 동등성 비교를 하며 불변성을 유지하기 때문입니다.
- 객체 내에서 값 객체로 표현 가능한 부분은 VO로 변환할 수 있습니다. 예를 들어, Address, Money, Email 등은 VO로 변환하여 엔티티의 일부로 사용할 수 있습니다.
- 객체를 값 객체로 변경할 수 있는 상황은?
- 상태 변경이 불필요한 경우: 객체의 일부 속성이 상태 변경이 필요 없는 경우, 이를 값 객체로 변환할 수 있습니다. 예를 들어, Address 엔티티가 Street, City, ZipCode 같은 정보를 포함하고 있을 때, 이들 정보가 한 번 설정되면 변하지 않는다면 Address를 값 객체로 변경할 수 있습니다.
- 값 객체로 정의될 수 있는 경우: 예를 들어 Money나 DateRange와 같이 값에 의한 동등성 비교가 중요한 불변 객체라면, 값 객체로 정의될 수 있습니다. 이 경우, 식별자가 필요하지 않으며, 단순히 값 자체가 중요한 객체입니다.
//Object
public class Address {
private Long id; // 엔티티 식별자
private String street;
private String city;
private String zipCode;
public Address(String street, String city, String zipCode) {
this.street = street;
this.city = city;
this.zipCode = zipCode;
}
// getter, setter 메서드
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getZipCode() {
return zipCode;
}
public void setZipCode(String zipCode) {
this.zipCode = zipCode;
}
}
//VO
public class Address {
private final String street;
private final String city;
private final String zipCode;
public Address(String street, String city, String zipCode) {
this.street = street;
this.city = city;
this.zipCode = zipCode;
}
// getter 메서드
public String getStreet() {
return street;
}
public String getCity() {
return city;
}
public String getZipCode() {
return zipCode;
}
// 동등성 비교 (값 기준)
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Address address = (Address) obj;
return street.equals(address.street) && city.equals(address.city) && zipCode.equals(address.zipCode);
}
@Override
public int hashCode() {
return Objects.hash(street, city, zipCode);
}
}
참고자료
728x90
'Computer Science > Software Design' 카테고리의 다른 글
디자인 패턴 (3) Strategy Pattern (0) | 2024.09.26 |
---|---|
디자인 패턴의 종류 (2) Factory Pattern (0) | 2024.09.26 |
디자인 패턴의 종류 (1) Singleton Pattern (0) | 2024.09.25 |
아키텍처 패턴의 종류 (5) MVC Pattern (0) | 2024.07.04 |
아키텍처 패턴의 종류 (4) Sense-Compute-Control Pattern (0) | 2024.07.04 |