티스토리 뷰
객체지향 생활 체조 원칙에는 9가지의 원칙이 존재한다.
그 중에 규칙 8을 인용해보면 다음과 같은 규칙이 존재한다.
규칙8. 일급 컬렉션을 사용하라
이 규칙의 적용은 간단하다. 컬렉션을 포함한 클래스는 반드시 다른 멤버 변수가 없어야 한다. 컬렉션은 매우 유용한 원시타입이다. 많은 동작이 있지만 후임 프로그래머나 유지보수 담당자에 의미적 의도나 단초는 거의 없다.
간단하게 먼저 일급 컬렉션을 보여드리자면 다음과 같다.
List<Integer> list = new ArrayList<>();
list.add(3);
list.add(4);
list.add(6);
list.add(1);
위 코드를 객체로 한번 감싸 주는 것이다.
public class PeopleQueue {
private List<Integer> peoples;
public PeopleQueue(List<Integer> peoples) {
this.peoples = peoples;
}
}
위와 같이 Wrapping을 해주면서 외에 다른 멤버 변수가 없는 상태가 바로 일급 컬렉션이다. 위와 같은 구조는 여러 장점이 존재한다.
- 비지니스에 종속적인 자료구조
- Collection의 불변성을 보장
- 상태와 행위를 한 곳에서 관리
- 이름이 있는 의미가 존재하는 컬렉션
이제 하나 하나 살펴보자
1. 비즈니스에 종속적인 자료구조
/**
* 동욱님의 예제를 빌려와 사용하고 있습니다.
* 출처 - https://jojoldu.tistory.com/412
*/
public class LottoService {
private static final int LOTTO_NUMBERS_SIZE = 6;
public void createLottoNumber() {
List<Long> lottoNumbers = createNonDuplicateNumbers();
validateSize(lottoNumbers);
validateDuplicate(lottoNumbers);
}
private void validateDuplicate(List<Long> lottoNumbers) {
Set<Long> checkNum = new HashSet<>(lottoNumbers);
if (checkNum.size() != LOTTO_NUMBERS_SIZE) {
throw new IllegalArgumentException("로또 번호는 중복될 수 없습니다.");
}
}
private void validateSize(List<Long> lottoNumbers) {
if (lottoNumbers.size() != LOTTO_NUMBERS_SIZE) {
throw new IllegalArgumentException("로또 번호는 반드시 6개여야 합니다.");
}
}
}
위 코드는 잘 작동하는 6개의 로또 번호를 받게 해주는 프로그램이다. 하지만 이 경우 좋지 못한 코드가 된다. 생각해보자
이렇게 설계된 코드는 로또 번호가 필요한 모든 장소에 검증 로직이 들어가야 한다. 그리고 새로 코드를 본 사람들은 이게 왜 검증이 필요한지 알지도 못한다.
한 마디로 만든 사람만 알 수 있는 코드이다. 그렇다면 이를 해결해보자
/**
* 동욱님의 예제를 빌려와 사용하고 있습니다.
* 출처 - https://jojoldu.tistory.com/412
*/
public class LottoTicket {
private static final int LOTTO_NUMBERS_SIZE = 6;
private final List<Long> lottoNumbers;
public LottoTicket(List<Long> lottoNumbers) {
validateSize(lottoNumbers);
validateDuplicate(lottoNumbers);
this.lottoNumbers = lottoNumbers;
}
}
보면 확연하게 달라졌다. 우선 이 구조가 일급 컬렉션의 기본적인 구조라는 것을 알 수 있다. 아예 생성에 조건을 달아 6개의 숫자로만 이루어져야 하고 중복되지 않아야만 하는 로또 데이터가 생성된다.
이제 로또 번호가 필요한 모든 로직은 이 일급 컬렉션만 있으면 해결 된다. 내부는 알 필요가 없어졌다.
public class LottoNumberService {
private final int LOTTO_SIZE = 6;
public void createNumber() {
LottoTicket lottoTicket = new LottoTicket(createNonDuplicateNumbers());
}
}
필요한 로직은 모두 LottoTicket으로 좀 더 비즈니스에 종속된 자료구조가 만들어져 발생할 문제를 최소화 하였다.
2. 불변
일급 컬렉션은 컬렉션의 불변을 보장한다.
final이 자료구조에 불변을 보장해준다는 큰 오해가 있지만 전혀 그렇지 않다.
다음 테스트를 해보자
@Test
@DisplayName("final도 값의 변경이 가능하다.")
void finalCollectionTest() {
// given
final Map<String, Boolean> collection = new HashMap<>();
// when
collection.put("1", true);
collection.put("2", true);
collection.put("3", true);
collection.put("4", true);
// then
assertThat(collection.size()).isEqualTo(4);
}
테스트가 실패할 것 처럼 생겼다 하지만 이 테스트는 성공한다.
이유는 간단하다 final 키워드는 여기서 재할당만 금지되어 있을 뿐 값을 넣는데는 아무런 영향을 주지 않는다.
현 시점 멀티 쓰레드를 지원하는 자바에서 큰 규모의 프로젝트를 하게되면 불변 객체는 아주 중요해지고 있다. 불변성을 지킬 수록 사이드 이펙트가 생기는 걸 최소화 할 수 있기 때문이다.
public class Member {
private final List<Follower> followerList;
public Member(List<Follower> followerList) {
this.followerList = followerList;
}
public long countFollower() {
return followerList.size();
}
public List<Follower> getFollowerList() {
return followerList;
}
}
다음 코드를 보면 내부에 어떠한 변경에 대한 내용이 존재하지 않는다. 그렇기에 변경에 대해 안전하다. 그리고 또한 내부 List가 private 으로 접근이 안되기 때문에 내부에서는 List에 접근을 할 수 없다.
Java에서는 final로 불변을 지킬 수 없기 때문에 일급 컬렉션과 래퍼 클래스등으로 해결해야 한다.
하지만 뒤에서 설명하겠지만 이건 완전 맞는 설명은 아니다. 추후에 다시 살펴보도록 하겠다.
3. 상태와 행위를 한 곳에서 관리
class MemberTest {
@Test
@DisplayName("일반 컬렉션일 땐 중복코드가 발생한다.")
void originCollection() {
// given
List<Follower> followerList =
List.of(new Follower(10, "김동빈", 23),
new Follower(20, "신길석", 24),
new Follower(30, "조예빈", 25),
new Follower(25, "김기석", 24));
// when
Long followerFilterByAge = followerList.stream()
.filter(follower -> follower.getAge() == 24)
.count();
// then
assertThat(followerFilterByAge).isEqualTo(2L);
}
}
코드를 살펴보면 너무 안타까운 부분이 많다. 우선적으로 Collection에는 내부에 나이에 따라 내부에서 계산을 해주는 로직이 없기 때문에 필터링 해야하는 나이에 따라 로직을 다르게 해줘야 하는 번거로움이 생긴다. 그리고 그로인해 중복 코드가 남발되는 아주 기분 나쁜 상황이 보여질 가능성이 높다.
하지만 일급 컬렉션은 이걸 단번에 해결해준다. 정말 놀랍지 않은가!! 그렇다면 해결한 코드를 살펴보자.
public class Member {
private final List<Follower> followerList;
public Member(List<Follower> followerList) {
this.followerList = followerList;
}
// 나이를 기준으로 count하는 함수 추가
public long countByAge(int age) {
return followerList.stream()
.filter(follower -> follower.getAge() == age)
.count();
}
}
다음과 같은 함수만 추가해준다면 준비는 끝이다. 이제 테스트를 통해 어떻게 변하는지 확인해보자.
@Test
@DisplayName("일급 컬렉션은 내부 메서드를 통해 이 문제를 해결한다.")
void firstCollection() {
// given
List<Follower> followerList =
List.of(new Follower(10, "김동빈", 23),
new Follower(20, "신길석", 24),
new Follower(30, "조예빈", 25),
new Follower(25, "김기석", 24));
Member member = new Member(followerList);
// when
long followerFilterByAge = member.countByAge(24);
// then
assertThat(followerFilterByAge).isEqualTo(2L);
}
정말 간단하게 member라는 일급 컬렉션에 담아 내부 함수만 실행해 준다면 완료이다. 얼마나 간단한가 이 처럼 일급 컬렉션을 사용하면 상태와 로직이 한 곳에서 관리되는 편리함을 맛볼 수 있다.
4. 컬렉션에 의미를 부여
컬렉션은 List Map Set Stack Queue 같은 원시적인 명칭일 뿐 세부적인 의미를 담을 수 없다. 그렇기 때문에 단순 컬렉션만으로 개발을 한다면 후에 인수인계 할 때 하나 하나 사용법 도메인에 대해 설명해야한다. 이를 좀 더 쉽게 하고 싶을 것이다.
일급 컬렉션은 바로 그걸 단번에 해결해준다. 한번 비교해보자
List<Friend> followers = createFollower();
List<Friend> followings = createFollowing();
다음과 같은 경우 두 가지 문제가 있다.
- 검색이 불편 : 변수명을 자신이 정의해놓지 않았다면 해당 소스를 찾는데 시간이 소요된다.
- 의미를 부여하기 힘들다 : 변수명에 불과하기 때문에 어떠한 의미를 부여하기란 쉽지 않다.
일급 컬렉션을 이용해서 어떻게 해결할까?
Followers followers = new Followers(createFollower());
Followings followings = new Followings(createFollower());
더 명확해 졌다. 컬렉션 클래스를 찾으면 해당 로직을 찾기란 더 쉬워질 것이고 당연히 내부 메소드를 통해 내부적인 동작에 대한 점도 쉽게 알 수 있게 된다.
정리
필자는 그렇게 생각한다. 개발자는 결국 사람이고 실수를 할 수 밖에 없다. 그리고 객체지향과 자바언어에 등장은 이러한 실수들을 방지하기 위해 여러 규칙을 만들고 있다.
또한 규모가 큰 프로젝트에 적합한 객체지향 특성상 협업을 위한 코드도 필요하다. 그렇기 때문에 일급 컬렉션도 비슷한 레파토리로 등장하게 된 것이라고 생각된다.
일급 컬렉션으로 표현을 명확히 하여 처음 투입된 개발자들이더라도 이 컬렉션은 어디에 쓰이는지 바로 알 수 있고, 상태와 행위를 한 곳에서 관리하기 때문에 일급 컬렉션 내부를 살펴보면 동작 흐름 개발의도를 쉽게 알 수 있다.
Reference.
https://jojoldu.tistory.com/412
'Java' 카테고리의 다른 글
Abstract와 Interface사이에서 발생할 수 있는 오해(?) (0) | 2022.02.16 |
---|---|
HashMap과 HashTable의 차이 (0) | 2022.02.16 |
HashMap은 어떤 구조일까? (0) | 2022.02.13 |
[자바 고급 스터디 2주차 - 1부] Wrapper Class (0) | 2022.02.13 |
[자바 고급 스터디 1주차 - 3부] Stream과 Optional (0) | 2022.02.10 |
- Total
- Today
- Yesterday
- DevOps
- Spring
- 취준
- Kotlin
- Redis
- 취업
- JPA
- 동시성
- docker
- 코딩
- swarm
- 프로그래밍
- 코드
- java
- thread
- DB
- 백엔드
- 면접
- 개발
- 인터뷰
- IT
- 취업준비
- 면접 준비
- MySQL
- 프로젝트
- CS
- 면접준비
- 개발자
- 자바
- 게시판
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |