-
🧑🏻💻 이동욱님의 글을 보고 공부용으로 정리했습니다.
⭐ 일급 컬렉션
Collection을 Wrapping 하면서, 그 외 다른 멤버 변수가 없는 상태를 일급 컬렉션이라 한다.
Map<String, String> map = new HashMap<>(); map.put("1", "A"); map.put("2", "B"); map.put("3", "C"); public class GameRanking { private Map<String, String> ranks; public GameRanking(Map<String, String> ranks) { this.ranks = ranks; } }
🌟 Wrapping의 이점
- 비지니스에 종속적인 자료구조
- Collection의 불변성 보장
- 상태와 행위를 한 곳에서 관리
- 이름이 있는 컬렉션
1️⃣ 비지니스에 종속적인 자료구조
📖 숫자 야구의 2가지 조건
- 3개의 번호로 이루어져있다.
- 3개의 번호는 1 ~ 9 까지의 서로 다른 임의의 수이다.
🧑🏻💻 구현
public class BaseBallApp { private static final int DIGIT = 3; ... if(!InputValidator.isValidatedAnswer(DIGIT, inputAnswer)) { throw new IllegalArgumentException( String.format("%d자리 숫자가 아닌 잘못된 입력값 입니다. - %s", DIGIT, inputAnswer)); } ... }
- 비지니스 로직을 서비스 부분에서 처리
⚠️ 문제
숫자 야구 번호가 필요한 곳에서 계속해서 검증로직이 들어가야 한다.
- 모든 코드에 대해 알고 있지 않다면 문제가 발생할 수 있다.
💡 숫자 야구의 조건을 만족하는 자료구조를 직접 만들자.
⇒ 해당 조건으로만 생성할 수 있는 자료구조
public class BaseBallNumber { private static final int DIGIT = 3; private final int[] numbers; public BaseBallNumber(int[] numbers) { validationSize(DIGIT, numbers); validationDuplicated(numbers); this.numbers = numbers; } ... }
- 이런 클래스를 일급 컬렉션이라고 한다.
2️⃣ 불변
일급 컬렉션은 컬렉션의 불변을 보장한다.
📖 final 키워드
- Java 에서의 final 은 불변을 만들어주는 것이 아니다 ❌, 재할당만 금지한다
🌟 불변 객체의 중요성
- 객체의 값이 절대 바뀔일이 없다는 것이 보장되면 코드를 이해하고 수정하는 사이드 이펙트가 최소화된다.
Java에서는 final로 불변 객체를 만들 수 없기 때문에 일급 컬렉션과 래퍼 클래스등의 방법으로 해결해야 한다.
public class BaseBallNumber { private static final int DIGIT = 3; private final int[] numbers; public BaseBallNumber(int[] numbers) { validateSize(numbers); validateWithinRange(numbers); this.numbers = numbers; } public void calculate(BiConsumer<Integer,Integer> biConsumer){ for(int i = 0; i < numbers.length; i++) { biConsumer.accept(numbers[i], i); } } }
이렇게 값을 변경할 수 있는 메소드가 없는 컬렉션을 만들게 되면 해당 컬렉션은 불변 컬렉션이 된다.
3️⃣ 상태와 행위를 한 곳에서 관리
- 값과 로직이 함께 존재
ex) 여러 Pay 방법들이 있을 때, NaverPay 금액의 합이 필요한 경우
List<Pay> pays = Arrays.asList( new Pay(NAVER_PAY, 1000), new Pay(KAKAO_PAY, 1500), new Pay(NAVER_PAY, 2000), new Pay(KAKAO_PAY, 2000)); Long naverPaySum = pays.stream() .filter(pay -> pay.getPayType().equals(NAVER_PAY)) .mapToLong(Pay::getAmount) .sum();
- Pay라는 컬렉션과 계산하는 로직에 관계가 존재한다.
⚠️ 문제
- 똑같은 기능을 하는 메소드가 중복되어 생성될 수 있다.
- 메소드를 누락할 수 있다.
- 다른 결과가 필요한 경우 더 코드가 흩어질 수 있다.
public class PayGroups { private List<Pay> pays; public PayGroups(List<Pay> pays) { this.pays = pays; } public Long getNaverPaySum() { return pays.stream() .filter(pay -> PayType.isNaverPay(pay.getPayType())) .mapToLong(Pay::getAmount) .sum(); } public Long getKakaoPaySum() { return getFilteredPays(pay -> PayType.isKakaoPay(pay.getPayType())); } private Long getFilteredPays(Predicate<Pay> predicate) { return pays.stream() .filter(predicate) .mapToLong(Pay::getAmount) .sum(); } }
- PayGroups 라는 일급 컬렉션을 통해 상태와 로직이 한곳에서 관리될 수 있게 되었다.
List<Pay> pays = Arrays.asList( new Pay(NAVER_PAY, 1000), new Pay(KAKAO_PAY, 1500), new Pay(NAVER_PAY, 2000), new Pay(KAKAO_PAY, 2000)); PayGroups payGroups = new PayGroups(pays); Long naverPaySum = payGroups.getNaverPaySum();
4️⃣ 이름있는 컬렉션
컬렉션에 이름을 붙일 수 있다.
- 일급 컬렉션을 만들면 해당 컬렉션을 기반으로 용어 사용과 검색을 하면 된다.
📖 참고자료
'☕️ java' 카테고리의 다른 글
@ParameterizedTest (0) 2023.10.25 👀 Unit Test 네이밍 컨벤션 (0) 2023.10.24 🧪 JMH (Java Microbenchmark Harness) (1) 2023.10.23 🆚 Random 함수 비교 (0) 2023.10.23 DAO DTO / VO (0) 2023.03.10