일급 컬렉션을 잡았..나..?🤔

김민제·2023년 11월 28일
0
post-thumbnail
  • 객체 지향적인 프로그래밍을 하기 위해 공부하다보면 일급컬렉션을 사용하라는 얘기가 정말 많은 것 같습니다..
  • 객체 지향 생활체조의 8번 규칙에서는 일급 컬렉션을 사용해야한다고 하고 있습니다.
  • 규칙 8을 짧게 보자면 이렇게 정의되어 있습니다.

규칙 8 : 일급컬렉션 시용

  • 이 규칙의 적용은 간단하다.
  • 컬렉션을 포함한 클래스는 반드시 다른 멤버 변수가 없어야 한다.
  • 필터는 또한 스스로 함수 객체가 될 수 있다.
  • 또한 새 클래스는 두 그룹을 같이 묶는다든가 그룹의 각 원소에 규칙을 적용하는 등의 동작을 처리할 수 있다.
  • 컬렉션은 실로 매우 유용한 원시 타입이다.

  • collection을 Wrapping하면서 그 외 다른 멤버 변수가 없는 상태를 일급 컬렉션이라 합니다.
  • 일급 컬렉션은 컬렉션을 다루는 하나의 패턴이자 컬렉션을 감싸는 단일 클래스를 만들고 해당 클래스를 통해 컬렉션과 관련된 모든 로직을 처리하는 것입니다.
  • 그러면 일급 컬렉션의 이점을 기준으로 일급 컬렉션을 알아보도록 하겠습니다.

일급 컬렉션 장점

  • 일급 컬렉션의 대표적인 장점은 다음과 같습니다.
  1. 비즈니스에 종속적인 자료구조

  2. Collection의 불변성을 보장

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

  4. 이름이 있는 컬렉션

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

  • 일급 컬렉션을 생성하면 특정 조건을 부여하여 만들 수 있는 새로운 자료구조를 생성할 수 있습니다.
  • 제가 지금 진행하고 있는 프리코스 2주차 자동차 경주 게임의 예를 들어보겠습니다.
  • 자동차의 이름을 5자 이하로 받아야하며 중복된 이름이 있으면 안된다라는 제한이 있다고 생각해봅시다.
  • 그러면 유효성 검사를 위한 메소드를 만들고 이름을 받아 리스트를 생성할 때 유효성 검사를 해주어야할 것입니다.
  • 자동차 이름에 대한 유효성 검사는 자동차 이름이 필요한 곳에서는 모두 해줘야 합니다.
  • 하지만 이러한 로직들이 많아지고 코드들이 많아지면 이러한 자동차 이름에 대한 유효성을 언제 검사해야하고 이 검증 로직이 필요한지 헷갈리게 될 것입니다.
  • 이를 해결하기 위해 일급 컬렉션이 사용됩니다.
  • 아래는 일급 컬렉션을 적용하지 않은 코드입니다.
public class CarList {

    public carList() {
		    private List<Car> carList = createCarList();
        nameSize_check(carList);
        moveTypeCheck(carList);
    }

    public void nameSize_check(List<Car> carList) {...}
    public void moveTypeCheck(List<Car> carList) {...}

}
  • 이와 같이 작성할 경우 carList가 필요한 모든 곳에 검증 로직이 추가되어야 합니다.
  • 아래는 일급 컬렉션을 적용한 코드입니다.
public class CarList {
    private List<Car> carList;

    public CarList(List<Car> carList) {
        nameSize_check(carList);
        moveTypeCheck(carList);
        this.carList = carList;
    }

    public void nameSize_check(List<Car> carList) {...}
    public void moveTypeCheck(List<Car> carList) {...}

}
  • 위에서 설명했듯이 멤버 변수는 Collection 한 개밖에 없고 생성자를 통해 이 멤버 변수에 특정한 조건을 만족할 때 List가 생성되도록 해주었습니다.
  • 이로써 비즈니스에 종속적인 자료구조가 만들어져 자동차 이름이 필요한 로직은 이 일급 컬렉션만 있으면 사용할 수 있게 되었습니다.
CarList carList = new CarList(createCarList());

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

  • 일급 컬렉션 또 다른 장점은 값과 로직이 함께 존재한다는 것입니다.
  • 이에 대한 예제는 제가 직접 만들어가며 공부하기가 어려울거 같아 이해를 돕기 위해 향로님의 일급 컬렉션 (First Class Collection)의 소개와 써야할 이유를 참고하여 예제에 대한 설명을 제가 알기 편한 방식으로 정리를 하였습니다.
  • 네이버페이의 합이 필요하다고 가정해봅니다.
  • 그러면 이런 식으로 합을 구할 것입니다.
List<Pay> pays = Array.asList(
	new Pay(NAVER_PAY, 1000),
	new Pay(NAVER_PAY, 2000),
	new Pay(KAKAOPAY, 2000)
);

Long naverPaySum = pays.stream()
	.fileter(pay -> pay.getPayType().equals(NAVER_PAY))
	.sum();
  • 이 코드의 문제는 pays와 합 로직의 관계 표현이 안된다는 것과 똑같은 기능을 하는 메소드를 중복 생성할 수 있다는 점 등이 있고 만약 네이버페이 외에 카카오 페이, 배민 페이 등의 총금액도 필요하다면 더더욱 코드가 흩어질 확률이 높다는 문제점이 있습니다.
  • 위 문제를 해결하기 위해 네이버 페이의 합을 구하는 코드는 다음과 같습니다.
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();
    }
}
  • 이렇게 PayGroups이라는 일급 컬렉션이 생기며 상태와 로직이 한 곳에서 관리되고 필요에 따라 메소드를 추가하고 공통 부분을 뽑아내어 페이의 종류를 늘려줄 수 도 있을 것입니다.

3. **이름이 있는 컬렉션**

  • 마지막 장점은 컬렉션에 이름을 붙일 수 있다는 것입니다.
List<Student> freshman = createFrashMan();
List<Student> sophomore = createSophomore();
List<Student> junior = createJunior();
List<Student> senior = createSenior();
  • 위와 같은 컬렉션들에게 이름을 줄 수 있습니다.
FrashMan freshman = createFrashMan();
Sophomore sophomore = createSophomore();
Junior junior = createJunior();
Senior senior = createSenior();
  • 코드 가독성 측면에서 조금 더 괜찮은 코드가 된 것 같습니다.

사용법 정리

사용해야할 때

  • 해당 컬렉션이 필요한 모든 장소에서 검증 로직이 들어갈 때 → 모든 데이터가 검증 로직이 필요하지 않을 수도 있고 새롭게 코드를 보는 사람은 모든 코드와 도메인을 알지 못하면 검증 로직이 필요한지 알 수 없을 수도 있습니다.
  • 값과 로직이 따로 있을 때 → 컬렉션과 계산 로직의 관계가 있는데 이를 코드로 표현할 수 없습니다. 컬렉션 타입의 상태에 따라 지정된 메소드에서만 계산되길 원할 때 강제할 수 있는 수단이 없습니다.
    • 이렇게 되면 똑같은 기능을 하는 메소드를 중복 생성할 수도 있고 필요한 메소드를 누락할 수 있습니다.
    • 일급 컬렉션으로 컬렉션과 계산 로직을 함께 두면 관련된 코드들이 모일 수 있다.
  • 불변성이 보장되어야할 때 → final 키워드는 값의 재할당만 막기 때문에 컬렉션이 Map으로 선언되어 값이 다른 메소드에서 추가되어도 막을 수 없습니다.
    • 컬렉션의 값을 변경할 수 있는 메소드가 없는 컬렉션을 만들면 불변 컬렉션이 됩니다.

사용할 때

  • 불변을 보장해줍니다.
    • setter 메소드를 두지 않습니다.
    • 합당한 이유가 없다면 모든 필드를 private final로 선언합니다.
    • final 클래스로 선언합니다.
  • 인스턴스를 한 개만 생성하면 되는 상황이라면 생성자를 private으로 선언하여 싱글톤 패턴을 사용하면 좋을 것 같습니다.
  • 해당 값과 해당 값에 사용하는 로직을 한 곳에 담는다.
  • 생성자에서 검증 로직을 거친 뒤 값을 할당한다.

정리

  • 일급 컬렉션은 어려웠던 부분이 많았던 것 같습니다.
  • 특히 머리로는 이해가 가는데 이걸 어떻게 써야하는지 왜 이렇게 쓰면 이런 장점이 되는지 연결하는 부분에서 어려움을 겪어 시간을 많이 보낸 것 같습니다.
  • 일급 컬렉션을 공부한 후 제 코드에 적용해보려 고민을 많이 해보았습니다.
    • 차량을 생성하고 움직이는 클래스를 일급 컬렉션으로 만들어 상태와 행위를 한 곳에서 관리해보려고 생각해보았으나 클래스 내 메소드가 너무 많아지거나 한 클래스의 책임이 너무 많아지면 좋지 않은 코드가 될까봐 고민이 많습니다.
    • 아직 저의 코딩 실력과 일급 컬렉션에 대한 이해가 부족한 것에서 오는 고민이라고 생각하고 이 고민을 기회삼아 더 연습하고 노력해서 고민을 해결해보려고 합니다.
    • 한 가지 다행인 것은 이 문제를 해결하는 시점의 저는 큰 성장을 이뤘을 것 같아 그것을 기대하며 노력할 수 있을 것 같다는 점입니다.
  • 저는 일급 컬렉션을 “컬렉션을 감싸는 클래스에서 컬렉션과 관련된 모든 로직을 처리하는 것”이라고 이해했습니다.

+추가

  • 프리코스 미션들에서 일급 컬렉션을 사용하려고 노력하며 일급컬렉션에 대하여 많이 이해한 것 같습니다.
  • 우선 요구 사항을 보고 구현을 생각할 때 바로 일급 컬렉션을 사용할지 정하는 것에 익숙해지기 위해 사용해야 할 상황을 따로 정리하여 그 기준에 부합한다면 사용하려고 노력하고 있습니다.
  • 따로 이론적으로 공부를 많이 하는 것도 좋지만 직접 적용하며 공부하는 것이 더 잘 이해가 되는 것 같습니다!
  • 2주차 미션에서 차량을 생성하고 움직이는 클래스를 일급 컬렉션으로 만들어 상태와 행위를 한 곳에서 관리하면 메소드가 많아질까 고민했지만 생성하는 클래스를 따로 두고 일급 컬렉션에는 검증과 행위만 담으면 되니 더 효율적인 방법이 되었습니다!
  • 이 때까지 연습하며 일급컬렉션을 잘못되게 사용한 부분도 많았지만 사용할 때와 사용하는 방법에 대한 나름의 기준을 정하고 이를 생각하며 적용해보니 잘못된 사용을 하는 부분이 적어진 것 같습니다.
  • enum에 대한 내용도 이렇게 공부하여 적용을 하면 더 잘 적용할 수 있을 것 같습니다!
profile
블로그 이전했습니다!! 👉 https://alswp006.github.io/

0개의 댓글

관련 채용 정보