Computer Science/Software Design

Software Design : Object, DTO, VO 비교하기

마이트너 2024. 11. 25. 20:07

 

 


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);
    }
}

 

 


참고자료

VO(Value Ojbect)란 무엇일까?

728x90