전체 보기
🍀

13,14일차_일급 컬렉션과 레코드 적용해 리팩토링하기

작성일자
2023/10/31
태그
DIARY_DEVELOP
프로젝트
PreCourse
책 종류
1 more property

일급 컬렉션과 값 객체(VO)

이번 과제를 하면서 Car나 TryCount를 어떻게 관리할지 고민이 많았다. Car는 Cars라는 클래스로 감싸서 사용했는데 이가 일급 컬렉션과 관련이 있음을 알게 되었고 처음 들어보는 이 용어에 대해 학습했다.
그리고 TryCount 역시 일급 컬렉션으로 관리할 수 있을지 궁금했는데 글 끝에선 이는 값 객체와 관련됨을 알 수 있었다.
내가 좋아하는 이동욱님 글을 참고해 이번 과제에 적용해보며 생각해보았다.
소트웍스 앤솔로지 의 객체지향 생활체조 파트 내 규칙 8: 일급 콜렉션 사용 에 아래와 같은 내용이 나온다.
콜렉션을 포함한 클래스는 반드시 다른 멤버 변수가 없어야 한다. 각 콜렉션은 그 자체로 포장돼 있으므로 이제 콜렉션과 관련된 동작은 근거지가 마련된셈이다. 필터가 이 새 클래스의 일부가 됨을 알 수 있다. 필터는 또한 스스로 함수 객체가 될 수 있다. 새 클래스는 두 그룹을 같이 묶는다든가 그룹의 각 원소에 규칙을 적용하는 등의 동작을 처리할 수 있다. 이는 인스턴스 변수에 대한 규칙의 확실한 확장이지만 그 자체를 위해서도 중요하다. 콜렉션은 실로 매우 유용한 원시 타입이다. 많은 동작이 있지만 후임 프로그래머나 유지보수 담당자에 의미적 의도나 단초는 거의 없다.
한 줄로 요약하자면, 콜렉션을 포장하면서 그 외 다른 멤버 변수가 없는 상태일급 컬렉션이라 일컫는다.
이걸 이번 과제에 적용해보면 콜렉션이 Car고, Cars가 콜렉션을 포함한 클래스가 될 것이다. 코드로 보여주면 아래와 같다.
일급 컬렉션 사용
public class Cars { private List<Car> cars;
Java
복사
일급 콜렉션을 안 썼으면 아래와 같은 코드였을 것이다.
List<Car> cars = new ArrayList<>(); cars.add(new Car());
Java
복사
그렇다면 일급 콜렉션을 써서 얻는 이득이 뭘까? 코드로 보여주겠다.

컬렉션(Car)의 불변 보장

일급 컬렉션 사용 : 값 추가/변경 안됨
public class Cars { private final List<Car> cars; public Cars(List<Car> cars) { this.cars = cars; } public void move() { cars.stream().forEach(Car::move); // Method Reference 사용 } public List<Long> getPositions() { return cars.stream().map(Car::getPosition).toList(); } }
Java
복사
리스트 사용 : final로 재할당만 금지할 수 있을 뿐, 값 추가/변경이 가능함
@Test public void final도_값변경이_가능하다() { //given final List<Car> cars = new ArrayList<>(); //when cars.add(new Car()); //then assertThat(cars.size()).isEqualTo(1); // 성공 }
Java
복사

상태와 행위를 한 곳에서 관리

일급 컬렉션 사용 : 값과 로직이 함께 존재함
public class Cars { private final List<Car> cars; // 값 public Cars(List<Car> cars) { this.cars = cars; } public List<Long> getPositions() { return cars.stream().map(Car::getPosition).toList(); // 로직 } } List<Car> cars = Arrays.asList(new Car("빨간차"), new Car("파란차")); Cars cars = new Cars(cars); List<Long> carPosition = cars.getPositions();
Java
복사
리스트 사용 : 값과 로직이 흩어져 존재함
List<Car> cars = Arrays.asList(new Car("빨간차"), new Car("파란차")); // 값 List<Long> carPosition = cars.stream().map(Car::getPosition).toList(); // 로직
Java
복사

비즈니스에 종속적인 자료 구조

자동차 경주 게임의 조건 1
여러 대의 자동차가 존재
자동차들의 이름은 중복되지 않아야 함
→ 여러 대의 자동차로 이루어지고 자동차 이름이 중복되지 않는 자료구조 만들어 검증 로직을 정밀하게 관리
일급 컬렉션 사용
public class Cars { private final List<Car> cars; public Cars(List<Car> cars) { validateNameDuplication(cars); this.cars = cars; } private void validateNameDuplication(List<Car> cars) { List<String> carName = cars.stream().map(Car::getName).toList(); Set<String> nonDuplicateName = new HashSet<>(carName); if(nonDuplicateName.size() != cars.size()) { throw new IllegalArgumentException("자동차 이름들은 중복될 수 없습니다"); } } }
Java
복사
자동차 경주 게임의 조건 2
이동 시도 횟수(int)가 존재
이동 시도 횟수는 0 이상 이어야 함
→ 이동 시도 횟수로 이루어지고 그 값이 0 이상인 자료 구조 만들어 검증 로직 관리
값 객체(VO) 사용
public class TryCount { private final int count; public TryCount(int count) { validateTryCount(count); this.count = count; } private void validateTryCount(int count) { if(count < 0) { throw new IllegalArgumentException("이동 시도 횟수는 0 이상이어야 합니다."); } } public int getCount() { return count; } }
Java
복사

마치며

Car와 TryCount가 다른 듯 비슷해서 두 개를 표현할 방법을 찾아나가기 위한 여정 속에 새로운 클래스들을 알게 되어 기쁘다.
처음엔 일급 컬렉션이란 용어만 알고서 미션에 적용해보다가, 값 객체(VO)라는 것도 자연스레 알게 되었다.
미션을 하며 느낀 둘의 공통점과 차이점을 간략히 정리해보며 글을 마무리하겠다.
다만,,, 이제 와서 든 생각은 내 로직에선 TryCount의 경우 값을 줄이고 늘리면 좋을 거 같아 VO로 쓰긴 적합하지 않을 거 같다.. 만일 본인의 로직에선 TryCount를 변경하지 않고 사용할 거라면 VO로 사용해도 좋지 않을까 싶다.

일급 컬렉션 vs 값객체

공통점
공통
값 변경 불가 (불변성)
생성자에서 유효성 검증 (자가 유효성 검사)
차이점
일급 컬렉션
값 객체
의미
하나의 컬렉션만 멤버 변수로 가지는 클래스
변경 불가한 객체
목적
컬렉션 관련한 비즈니스 로직 한 곳에서 관리
특정 값을 표현해 값에 대한 로직과 유효성 캡슐화
구성
하나의 멤버 변수 + 컬렉션 다루는 메서드
하나 이상의 멤버 변수 + 값 다루는 메서드
특징
동등한 속성 가지면 객체 간 동등성 보장

record와 dto

이번 과제에서 Car의 이름과 위치를 쌍으로 전달할 일이 많았다. 이를 map으로 표현하는 방법도 있지만, 가독성이 현저히 떨어져 나만 알아보는 코드가 될 가능성이 있기에 이럴 땐 dto를 쓰는 게 좋다. dto를 만들고 놨더니 인텔리제이가 record로 바꿀 수 있다고 알려줬다.
record에 대해 처음 들어봤는데 dto와 비슷하되 가장 큰 차이점은 필드들이 불변으로 선언된단 점이다. dto는 급진적으로 봤을 땐 모든 필드를 public으로 둬도 괜찮다는 의견이 있을 정도로 변경에 취약하다. 하지만 record를 쓰면 데이터를 온전하게 전송할 수 있다.

Today in 프리코스

TIL 작성하기
몰입
구현 완성하기
테스트코드 짜기
Search
1일차_둘러보기, 환경설정하기
2023/10/19
DIARY_DEVELOP
1일차_둘러보기, 환경설정하기
2023/10/19
DIARY_DEVELOP