728x90

일급 컬렉션

일단 나는 넥스트스텝의 TDD, Clean Code with Java 12기를 하면서

여기서도 일급 컬렉션을 사용했었다.

참 웃겼던건 이것을 조금 응용을 했었어야 했는데 개념 자체도 자세하게 정리가 덜 된것 같았다.

도메인에서부터 차근차근 만들어가는 것에서는 어느정도 생각이 잘 들었지만,

기존 레거시 코드에 이런게 적용되어 있지 않고 뚱뚱하게 로직이 작성되어 있으면

그냥 넘어갔던게 흔했다.

객체지향 생활체조 원칙

소트웍스 앤솔러지에서 발췌된 객체지향 생활체조 원칙이다.

  • 규칙 1: 한 메서드에 오직 한 단계의 들여쓰기만 한다.
  • 규칙 2: else 예약어를 쓰지 않는다.
  • 규칙 3: 모든 원시값과 문자열을 포장한다.
  • 규칙 4: 한 줄에 점을 하나만 찍는다.
  • 규칙 5: 줄여쓰지 않는다(축약 금지).
  • 규칙 6: 모든 엔티티를 작게 유지한다.
  • 규칙 7: 2개 이상의 인스턴스 변수를 가진 클래스를 쓰지 않는다.
  • 규칙 8: 일급 컬렉션을 쓴다.
  • 규칙 9: 게터/세터/프로퍼티를 쓰지 않는다.

진하게 설정해 놓은 것들은 웬만하면 거의 적용하고 있는 규칙들이다.

단, 9번 규칙에서 게터는 어쩔수 없이 사용하게 되는것 같다.

여기서 이 8번 규칙을 적용하는게 위에서 말했던 이유이다.

일급 컬렉션을 쓴다.

이 규칙 적용하는 법은 간단하다.

컬렉션을 포함한 클래스는 반드시 다른 멤버 변수는 존재하지 않아야 한다.

그리고 그 컬렉션만 들어있기 때문에 해당하는 컬렉션만의 로직을 구현할 수 있는 분기점이 생성된 것이다.

예를 한가지 들어보겠다.

public class Car {
    private final String name;
    private final int yearModel;

    public Car(final String name, final int yearModel) {
        this.name = name;
        this.yearModel = yearModel;
    }

    public String getName() {
        return name;
    }
}

이런 자동차 객체를 정의 했을 때, 비즈니스 로직에서 이런 행위들을 했다고 쳐보자.

public class CarService {
    public List<Car> findCarName(final String carName) {
        //다른 비즈니스 로직 수행....
        //DB에서 이러한 데이터를 가져왔다고 가정
        List<Car> carList = Arrays.asList(
            new Car("아반떼", 2021),
            new Car("k3", 2018),
            new Car("그랜저", 2019),
            new Car("모하비", 2020),
            new Car("아반떼", 2017),
            new Car("아반떼", 2016),
            new Car("아반떼", 2015);
        );

        //다른 비즈니스 로직 수행....

        return carList.stream()
            .filter(car -> car.getName().equals("아반떼"))
            .map(car -> car.getName())
            .collect(Collectors.toList());
    }
}

이런식으로 하나의 로직이 어떤 곳에서 데이터를 불러오고

차들의 이름이 매개변수로 받는 carName인 애들만 추려서 리스트로 만드는

그런 로직이다.

간단해서 나눌 필요가 없다고 생각이 들 수도 있지만, 메소드를 하나의 일만 하게끔 분리했을 때 이런 모양이 나왔다면,

그리고 이 로직들이 안에 갇혀있으면 점점 뚱뚱해지면서 테스트하기가 힘들어지는 로직이 생기게 될 것이다.

그래서 아래와 같이 개선한다.

일급 컬렉션

public class Cars {
    private final List<Car> cars;

    public Cars(List<Car> cars) {
        this.cars = cars;
    }

    public List<Car> findAllByCarName(final String carName) {
        return cars.stream()
            .filter(car -> car.getName().equals("아반떼"))
            .map(car -> car.getName())
            .collect(Collectors.toList())
    }
}

Car객체의 리스트만을 멤버 변수로 갖고있는 일급 컬렉션인 Cars를 구현했고,

여기서 해당하는 비즈니스 로직을 정의해 주었다.

public class CarService {
    public List<Car> findCarName(final String carName) {
        //DB에서 이러한 데이터를 가져왔다고 가정
        Cars cars = new Cars(Arrays.asList(
            new Car("아반떼", 2021),
            new Car("k3", 2018),
            new Car("그랜저", 2019),
            new Car("모하비", 2020),
            new Car("아반떼", 2017),
            new Car("아반떼", 2016),
            new Car("아반떼", 2015);
        ));

        return cars.findAllByCarName(carName);
    }
}

해당하는 조건으로만 생성된 일급 컬렉션이 생겨서 비즈니스 로직에 의존하지 않고

순수 Car 리스트에 대한 테스트만 진행을 해볼 수 있게 되었다.

그리고 불변으로 만들어 주어야 무분별한 수정, 삽입이 안되게끔 만들어 줄 수 있다.

그러기 위해선 일급 컬렉션 객체에서 Getter, Setter같은 메소드들을 제외시키고,

오로지 생성자를 통한 생성, 별도로 구현한 로직외 값을 수정 불가 하게끔 정의해주고 사용한다.

이렇게 되면 일급 컬렉션 + 불변객체 가 성립이된다.

더 나아가서 지금은 차 브랜드를 현대, 기아 두개를 같이 넣었지만

KiaCars, HyundaiCars처럼 일급 컬렉션을 따로 만들어서 관리할 수도 있다.

이렇게 따로 만든채로 다른 비즈니스 로직을 수행하게끔 해줄 수도 있다.

하나 더 나아간다면 또 공통적으로 ~를 한다. 라는 기능이 존재할 경우 인터페이스를 놓고 구현해줌으로써

또 여러 과정을 거치는 방법도 있을 것이다.

마무리

이런 일급 컬렉션을 사용하는 것에 재미가 들렸고 완벽하게라고는 못하겠지만,

점차 쓰는 빈도가 늘어가면서 객체지향, 그리고 리팩토링에 불이 붙을것 같다.

728x90

'Java' 카테고리의 다른 글

Checked Exception, Unchecked Exception  (0) 2022.09.07
변성  (0) 2022.08.11
변수  (0) 2022.08.07
상태 패턴 적용  (0) 2022.08.07

+ Recent posts