💋 원시값 포장
원시값 포장은 String, int 등등 원시타입의 값을 이용해 속성을 표현하지 않고, 의미가 있는 객체로 포장한다는 개념이다.
String name = "깃짱"; // 원시타입으로 속성을 표현
Name name = new Name("깃짱"); // 원시값을 포장해서 속성을 표현
원시값 포장을 하면 클래스 내부에 검증 로직을 만들어서 스스로 상태를 관리하게 되어 책임을 분리할 수 있다. 결과적으로 객체의 책임이 더 명확해진다.
또 원시값 포장을 하지 않으면 외부에서 검증해야 하는데, 이렇게 되면 검증 과정을 거치지 않고 값을 사용할 수 있게 된다.
원시값 포장을 하면, 객체를 사용해야 하는 경우에 늘 내부에서 검증하기 때문에, 비즈니스 로직 전반에서 포장값을 사용함으로서 항상 신뢰를 확보할 수 있어 더 안정적이고 명확한 도메인을 만들 수 있다.
원시값 포장을 통해 유연하게 객체 내의 타입을 관리할 수도 있다. 예를 들어, Money에서 필드로 관리하던 int 대신 double로 관리해야 하게 되었다고 생각해 보자. 원시값 포장을 사용하지 않았다면, 모든 자료형을 double로 수정해야 겠지만, 원시값을 포장했다면 Money에 double을 파라미터로 받는 생성자를 하나 추가해주면 된다.
💋 VO(Value Object)
값으로 쓰일 수 있는 객체
도메인에서 1개, 혹은 여러 개의 속성을 묶어서 특정 값을 나타내는 객체를 말한다.
아래의 예시 코드에서 보면, 체스의 가로줄(File)과 세로줄(Rank)를 묶어 위치(Position)을 나타내는 객체를 생성했다.
public final class Position {
private final File file;
private final Rank rank;
public Position(final File file, final Rank rank) {
this.file = file;
this.rank = rank;
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final Position position = (Position) o;
return file == position.file && rank == position.rank;
}
@Override
public int hashCode() {
return Objects.hash(file, rank);
}
public File file() {
return file;
}
public Rank rank() {
return rank;
}
}
VO라고 불리려면 아래 두 가지 조건을 만족해야 한다.
1. equals & hashcode 메서드를 재정의해 동등성 비교가 가능하다.
2. 불변 객체이다.
두 조건에 대해 아래에서 자세히 설명하겠다!
1. equals & hashcode 메서드를 재정의해 동등성 비교가 가능하다.
예를 들어 설명하겠다.
public class Name {
private final String name;
public Name(String name) {
this.name = name;
}
}
이런 클래스가 있다고 생각해 보자.
나는 깃짱 수십년째 깃짱
final Name gitJjang1 = new Name("깃짱");
final Name gitJjang2 = new Name("깃짱");
gitJjang1과 gitJjang2는 같을까?
System.out.println(gitJjang1.equals(gitJjang2)); // false
왜 다를까..?
사람은 name 안에 적힌 저 "깃짱"으로 같은지 다른지 구분하지만, 자바는 다르다.
자바는 Name 뒤에 적힌 객체가 저장된 위치로 객체를 비교하는데, 이 경우 저장된 위치가 710, 711로 각각 달라 다른 객체라고 인지하게 되는 것이다.
equals 메서드를 재정의하면, 객체가 가진 속성값을 기준으로 속성이 같으면 같은 객체라고 인식할 수 있다. 이런 비교를 동등성 비교라고 한다. 어떤 속성값을 기준으로 같은 객체로 볼 것인지는 equals 재정의를 하는 과정에서 선택할 수 있다. 속성값(필드 변수)이 여러 개라면 그 중 1개가 같을 때 동등성 비교를 할 것인지, 모든 것이 같을 때 동등성 비교를 할 것인지 설정할 수 있다. equals 재정의는 IntelliJ에서 Alt + Ins를 누르고, 재정의를 선택하면 자동 생성할 수 있다.
그렇다면 hashCode 재정의는 뭐지?
해시코드는 해시 알고리즘에 의해 생성된 정수이다. 너무 말이 어려운디...
그냥 객체가 저장된 주소값을 바탕으로 엄청 복잡하게 계산해서 숫자 하나를 띡 내놓는데, 이 복잡한 계산은 항상 일정해서 같은 input에 대해서는 늘 같은 output을 내놓는다. 또 output을 통해 input을 유추할 수 없도록 한 방향의 계산만 가능하다.
예시를 들어보자면, 웹사이트에서 비밀번호를 저장할 때 내 비밀번호가 1234라면, 그걸 그대로 DB에 저장하는 용감한 개발자도 있겠지만, 대개 해시 알고리즘을 돌려서 sdrh2io12esd788e%%#@ 따위의 문자로 저장한다. sdrh2io12esd788e%%#@로 비번이 1234였구나! 알아낼 수는 없지만, 1234를 넣으면 언제나 sdrh2io12esd788e%%#@가 나온다. 사실 그냥 알아서 재정의 해주니깐 크게 신경쓸 필요가 없긴 하다 ㅎㅎ
중요한 건, 이 두 가지 메서드를 재정의하면 객체의 동등성 비교가 가능해진다는 것이다.
2. 불변 객체이다.
불변 객체는 내부 속성을 한 번 할당한 후 변경할 수 없는 객체이다.
불변 객체에 대해서는 할 말이 너무 많은데, 일단은 이 글을 참고하면 좋을 것 같다.
💋 원시값 포장 VS VO(Value Object)
원시값 포장은 원시 타입을 사용해 객체의 의미를 나타내지 않고, 의미있는 객체를 포장하는 행위이다.
원시값 포장을 하게 되면, 객체 내부에서 유효성 검사, 동등성 검사, 불변 등을 선택적으로 관리할 수 있다.
VO는 값 객체로, 도메인에서 한 개 이상의 속성을 묶어서 특성 값을 나타내는 객체이다.
VO는 필수적으로 유효성 검사, 동등성 검사, 불변을 통해 객체를 관리해야 한다.
VO를 만들기 위한 행위가 원시값 포장인데, 원시값 포장을 했다고 해서 동등성, 불변이 보장된다고 볼 수는 없다는 말이다!
두 용어가 좀 헷갈려서 함께 정리해 봤는데, 두 개념을 비교하기는 조금 애매하다.
헷갈리면 꼭 손톱깎이처럼 딱 잘라 구분할 필요가 있을까..?
원하는 속성을 잘 포장해서 사용하고, 필요한 경우 VO로도 만들어 사용하면 되는 것이다!
'JAVA' 카테고리의 다른 글
[JAVA] 좋은 코드가 되려면 꼭 지켜야 할 기본적인 컨벤션 (0) | 2023.04.13 |
---|---|
[JAVA] 제네릭(Generic)이란? (0) | 2023.04.12 |
[JAVA] 리스트(List) 정렬: Collections.sort() 코드를 뜯어보았다, Comparable (0) | 2023.03.27 |
[JAVA] 컴포지션(Composition): 조합이 뭘까? 컴포지션의 개념/사용, 상속을 사용한 코드를 조합을 사용한 코드로 바꿔보기! (4) | 2023.03.23 |
[JAVA] 함수형 프로그래밍: 개념, 스트림에서 부작용 없는 함수를 사용해야 하는 이유, 순수 함수 (20) | 2023.03.22 |