우아한 테크코스의 레벨1 로또 미션의 프로그래밍 요구 사항에
모든 원시값과 문자열을 포장한다.
라는 요구 사항이 있었는데 이를 충족 시키기 위해 원시 값 포장에 대해 공부하게 되었다.
원시값 포장은 도메인 객체를 나타내기 위해 Anti pattern(primitive(int, string 등등) 타입을 쓰는 나쁜 습관)을 피하기 위해 필요하다고 한다.
근데 저 말을 처음 읽었을 때는 선뜻 잘 이해가 가지 않았다.
왜 primitive 타입을 쓰면 안되는 거지??
일단 저 질문 자체부터가 잘못된 것이였다. 앞의 문장에서 주의 깊게 볼 사항은 primitive 타입을 사용하지 않는다. 가 아니라 도메인 객체를 나타내기 위해라는 부분이다.
PR을 날릴 때 위와 같은 질문을 했었는데 이에 대한 답은 다음과 같다.
원시값이 모델에서 중요한 의미를 갖기 때문
같은 Integer타입이라도 그 값이 도메인에서 중요한 의미를 갖는 다면 원시값을 포장하는게 프로그램의 가독성과 관리면에서, 나아가 유지보수면에서도 좋은 코드가 될 것이다.
public class Ball {
public static final int MIN_NUMBER = 1;
public static final int MAX_NUMBER = 45;
private static final String RANGE_EXCEPTION = "숫자의 범위는 1부터 45까지여야 합니다.";
private final int number;
public Ball(int number) {
validateNumberIsInRange(number);
this.number = number;
}
private void validateNumberIsInRange(int number) {
if (number < MIN_NUMBER || number > MAX_NUMBER) {
throw new IllegalArgumentException(RANGE_EXCEPTION);
}
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Ball ball = (Ball)o;
return number == ball.number;
}
@Override
public int hashCode() {
return Objects.hash(number);
}
}
위의 Ball 객체는 number라는 인트형 변수만을 인스턴스 변수로 갖고 있고 하는 일이라고는 유효성 검사밖에 없다. 하지만 해당 프로그램에서 로또 번호 하나는 중요한 의미를 갖고 있다고 판단해 원시 값을 포장했다.
원시값 포장에 대해 공부하면서 VO와 원시값 포장의 다른 점이 뭔지 궁금해졌다.
VO에 대해서는 들어만 봤지 모호한 개념이라서 개념부터 차근차근 살펴보기로 했다.
VO는 도메인에서 한 개 또는 그 이상의 속성들을 묶어서 특정 값을 나타내는 객체를 의미한다.
필요한 이유는 primitive 타입이 도메인 객체를 모델링하기 위해 충분하지 않기 때문이다. 가령 도메인에서 중요한 의미라고 가정된 Integer 변수는 도메인에서 의미있는 값으로 초기화 돼야 하는데 Integer만으로 이를 충족시키기에는 역부족이다. 이때 VO로 값을 묶어서 의미있게 사용해야 한다.
여기까지 봤을 때
그럼 원시값 포장과 다른 점은 뭐지?
라는 의문이 든다.
이제 VO의 필수 조건들을 살펴보자.
- equlas & hash code 메서드를 재정의해야 한다.
- 수정할 수 없는 불변 객체여야 한다.
위에서 정의된 Ball 객체는 VO이자 원시값 포장 객체이다. 변수를 final로 선언해 값의 변경을 막았고 equlas()와 hashCode()도 정의되어 있다.
하지만
public class Ball {
private int number;
public Ball(int number) {
this.number = number;
}
public void changeBall(int number) {
this.number = number
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Ball ball = (Ball)o;
return number == ball.number;
}
@Override
public int hashCode() {
return Objects.hash(number);
}
}
다음 코드는 원시값 포장이라고 할수는 있지만 VO는 아니다. 변수의 불변성이 보장되지 않기 때문이다.(변수를 set 하는 로직이 있기 때문)
학습을 하면서 일급 컬렉션이라는 개념에 대해서도 접하게 되었는데 이건 또 앞의 개념들과는 다르다.
일급 컬렉션이란 Collection을 Wrapping하면서, Wrapping한 Collection 외 다른 멤버 볌수가 없는 상태를 일컫는다.
아래 테코톡 링크가 일급 컬렉션에 대해 잘 설명해 주고 있다.
->
https://tecoble.techcourse.co.kr/post/2020-05-08-First-Class-Collection/
요약하자면 일급 컬렉션을 사용하는 이유는 컬렉션의 상태와 로직을 한 곳에서 따로 관리할 수 있다는 것이다.
원시값 포장, VO, 일급 컬렉션에 대해 공부하면서 과제 요구 사항의 모든 원시값과 문자열을 포장한다 라는 의미를 객체들의 책임과 협력에 대해 생각하고 이것들의 의미를 잘 나타내라 라는 말로 해석해 볼 수 있었다.
내가 생각한 원시값을 포장하라
라는 말의 결론:
VO나 일급컬렉션을 의미있게 사용해 domain객체를 명확하게 정의해라
이것이 맞는 지는 모르겠지만 과제를 하기 전보다 지금 한층 더 성장했다는 것은 확실한 것 같다.