자동차 경주 미션과 로또 미션을 진행하며 일급 컬렉션을 적용해보게 되었는데요
일급 컬렉션에 대한 설명은 이동욱님의 블로그 글 이 잘 정리되어 있어 참고하시면 좋을 것 같습니다
제가 실제 요구사항에 적용해보며 느꼈던 장점 위주로 작성하도록 하겠습니다.
일급 컬렉션이란?
일급 컬렉션은 소트웍스 앤솔러지 책의 객체지향 생활 체조 원칙에 언급되었습니다.
- 규칙 8: 일급 콜렉션을 쓴다.
요약하자면 Collection을 Wrapping하면서, 그 외 다른 멤버 변수가 없는 객체를 일급 컬렉션이라고 합니다.
특정 요구사항의 책임을 담당하는 컬렉션 객체를 관리할 수 있다.
로또 게임 요구사항 중 로또 티켓에 관한 요구사항이 있었는데요
- 로또하나는 6개의 로또번호로 구성된다.
- 당첨번호와 하나의 로또티켓을 비교해서, 몇등인지 알려준다.
위의 요구사항을 충족하기 위해서 로또 티켓을 컬렉션으로 나타낸다면?
List<LottoNumber> lottoTicket
해당 컬렉션이 수행할 책임, 로직은 이 컬렉션이 사용하는 곳곳에 중복되어 뿌려질 수 있습니다.
해당 컬렉션을 사용하는 곳마다 로또 티켓의 validation 처리, 비교, 관련된 다양한 기능의 로직이 중복되고 결합될 수 있습니다.
예를들면, 로또 티켓을 사용하는 곳마다
- 6개의 로또 번호 validation
- 몇개의 로또가 맞았는지 알려준다
위의 기능에 대한 코드가 중복되어 작성될 수 있습니다
이 책임을 LottoTicket이란 이름의 클래스로 집중한다면?
- 로직의 중복, 결합도가 감소할 수 있다
- 해당 객체의 요구사항이 변경되는 경우 이 객체만 수정하면 되므로 최소 수정만 발생할 수 있다
public class LottoTicket {
private final Set<LottoNumber> values;
//validation 책임
private void assertValues(Set<Integer> values) {
Objects.requireNonNull(values, "values는 Null이 되면 안됩니다.");
if (values.size() != LOTTO_NUMBERS_SIZE) {
throw new IllegalArgumentException(
String.format("values의 size는 %d 이여야 합니다. values.size() \"%d\"", LOTTO_NUMBERS_SIZE,
values.size()));
}
}
...
int getMatchedCount(LottoTicket lottoTicket) {
return (int) values.stream()
.filter(lottoTicket::contains)
.count();
}
}
내부 캡슐화의 장점이 있다
1. 내부 구현을 변경할 수 있다.
기획의 변경으로 인해 내부 자료구조를 List로 변경할 수도 있고 Set인터페이스를 구현한 다양한 Set 구현체를 사용할 수도 있습니다.
이렇게 책임을 가진 컬렉션이 다른 방식으로 변경된다면 클래스 내부 캡슐화를 했기때문에 최소비용의 수정 작업이 이루어지게 될 것입니다.
ex) 만약 중복 번호가 가능한 로또로 기획이 변경되었다면? (다양한 가정)
2. 내부 필드를 지킬 수 있다.
객체로 감싸지 않는다면 컬렉션 메서드 호출을 통해서 값이 변경될 수 있습니다. 일급 컬렉션 클래스를 사용해 해당 부분을 방어하고
만약 내부 value를 밖으로 내보낸다 하더라도 방어적 복사를 통해서 변경을 막을 수 있습니다
방어적 복사
public List<Car> getCars() {
return new ArrayList<>(cars);
}
참고