본문 바로가기
우아한테크코스/공부

값 객체(VO)

by 자바지기 2022. 2. 20.
반응형

자동차 경주 미션 중 다른 크루들이 값 객체를 사용하여 구현하는 것을 보았다.

값 객체를 알고 있었지만 사용하지 못한 부분에 대해 반성을 하면서 작성하는 반성문

 

도메인 주도 설계 철저 입문 책을 보고 정리하는 글입니다.

값 객체란?

시스템 특유의 값을 표현하기 위해 정의하는 객체

 

값의 성질

1. 변하지 않는다.

2. 주고받을 수 있다.

3. 등가성을 비교할 수 있다.

 

값 객체는 값의 성질을 가지고 있다.

 

1. 값은 변하지 않는다.

값은 변화하지 않는다. 우리가 값을 수정할 때는 새로운 값을 대입한다.

예를 들면,

 

String greet = "하이"
greet = "안녕"

 

greet의 값이 처음에는 "하이" 였지만 "안녕"으로 수정되었다. 그렇지만 이것을 값이 변화했다고 보지 않는다.

값은 "하이", "안녕"을 나타낸다. 즉 "하이"라는 값이 "안녕"이라는 값으로 변화한 것이 아니라

greet 변수 내의 값이 수정된 것이다. "하이", "안녕"이라는 값은 처음부터 끝까지 변하지 않는다.

 

여기서 주의해야 할 점은 값 자체를 수정할 수는 없다.

예시는 아래와 같다.

# 잘못된 코드

"하이" = "안녕"
System.out.println("하이") // 결과는 "안녕"????

변하지 않는 성질을 가지는 값은 값 자체를 수정할 수 없다.

 

그렇다면 값이 변하지 않는 성질의 장점은 무엇일까?

의도하지 않은 상태 변화를 막을 수 있다. 모르는 사이에 상태가 변화되어 버그가 발생하는 것을 방지할 수 있다.

 

값 객체 또한 변하지 않는다.

2.  값을 주고받을 수 있다.

String greet = "하이"
greet = "안녕"

위에서 보았던 코드를 다시 한번 확인해보면, 

대입을 통해 값을 주고 받을 수 있다. 이를 통해 변수의 값이 수정된다.

 

값 객체 또한 마찬가지이다.

값 객체를 갖는 변수를 수정하기 위해서는 값 자체를 주고받아야 한다.

var name = new Name("박성우");
name = new Name("배카라");

 

3. 등가성을 비교할 수 있다.

다음과 같이 값은 등가성을 비교할 수 있다.

0 == 1
'a'.equals('a')

 마찬가지로 값 객체 또한 등가성을 비교할 수 있다.

var name = new Name("박성우")
name.equals(new Name("박성우"))

위는 값 객체의 등가성을 비교하는 예시이다.

값 객체가 같은 값을 가지므로 위의 예시는 true를 반환해야 한다.

따라서 값 객체 클래스를 구현할 때 equals 메서드를 오버 라이딩해야 한다.

 

값 객체로 정의해야 하는 기준

1. 규칙이 존재하는가

2. 낱개로 다루어야 하는가 

 

이번 자동차 경주 미션에서는 

Car의 String name 필드 변수를 값 객체로 만들었으면 좋았을 것이다.

이번 미션에는 "자동차 이름은 5자 이하여야 한다"라는 조건이 있었다.

다음과 같이 Name 클래스를 작성할 수 있다.

public class Name {
	String name;
    
    public Name(String name) {
    	validateNameLength(name);
        this.name = name;
    }

	private void validateNameLength(String name) {
    	if (name.length() > 5) {
        	throw new IllegalArgumentException();
        }    
    }
}

 

이 값 객체를 사용한다면 다음과 같은 이점을 얻을 수 있다.

 

1. 무결성의 유지

이름을 생성할 때 이름의 길이가 5 이하여야 한다는 조건을 정확하게 지킬 수 있다.

값이 유효한 지에 대한 여부를 항상 확인할 수 있다.

 

2. 로직이 코드 이곳저곳에 흩어지는 것을 방지한다.

이번 미션에서는 다음과 같이 Car 클래스 내부에서 자동차 이름에 대한 유효성 검증을 하였다.

public Car(String name, int position) {
    checkCarNamesBlank(name);
    checkCarNamesLength(name);
    this.name = name;
    this.position = position;
}

private void checkCarNamesBlank(String name) {
    if (name == null || name.isEmpty()) {
        throw new IllegalArgumentException(NULL_EMPTY_CAR_NAME_ERROR_MESSAGE);
    }
}

private void checkCarNamesLength(String name) {
    if (name.length() > MAX_CAR_NAME_LENGTH) {
        throw new IllegalArgumentException(CAR_NAME_LENGTH_ERROR_MESSAGE);
    }
}

이 코드처럼 이름에 대한 검증을 Car 클래스 내부에서 했을 때, 이름에 대한 추가적인 규칙이 생긴다면 

Car 클래스 내부에 그 규칙을 추가해야 한다.

따라서 Car 클래스가 지저분해지게 된다.

 

위의 코드보다는 값 객체를 나타내는 Name 클래스를 생성하여서 이름에 대한 로직을 모아둘 수 있게 해야한다.

 

반응형

댓글