본문 바로가기
객체지향

🤔 일급 컬렉션을 써야하는 이유

by 순원이 2024. 11. 12.

1. 😱 중복된 검증 로직의 지옥에서 탈출하기

문제 코드

//일급 컬렉션을 사용하지 않을 시
public class RacingGame {
    public void race(List<Car> cars) {
        if (cars == null || cars.isEmpty()) {
            throw new IllegalArgumentException("자동차가 없습니다.");
        }
        if (cars.size() > 8) {
            throw new IllegalArgumentException("자동차가 너무 많습니다.");
        }
        // 게임 로직...
    }

    public void announceWinner(List<Car> cars) {
        // 여기도 검증이 필요합니다
        if (cars == null || cars.isEmpty()) {
            throw new IllegalArgumentException("자동차가 없습니다.");
        }
        // 우승자 발표 로직...
    }
}

  • 매 메서드 작성시 검증 로직의 필요성을 고민해야 합니다
  • 동일한 검증 로직이 여러 곳에 흩어져 있습니다
  • 검증 로직 변경 시 모든 곳을 수정해야 합니다

일급 컬렉션 적용

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

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

    private void validateCars(List<Car> cars) {
        if (cars == null || cars.isEmpty()) {
            throw new IllegalArgumentException("자동차가 없습니다.");
        }
        if (cars.size() > 8) {
            throw new IllegalArgumentException("자동차가 너무 많습니다.");
        }
    }
}

2. 🔒 불변성 보장으로 얻은 안정성

문제 코드

public class RacingGame {
    private List<Car> cars;  // 여러 곳에서 수정이 가능한 상태입니다

    public void addCar(Car car) {
        cars.add(car);  // 경주 중에도 차량 추가가 가능합니다
    }

    public void removeCar(Car car) {
        cars.remove(car);  // 경주 중에 차량 제거도 가능합니다
    }
}

  • 경기 진행 중에도 자동차 추가/제거가 가능합니다
  • 여러 메서드에서 컬렉션 수정이 가능하여 디버깅이 어렵습니다
  • 특정 시점의 컬렉션 상태를 예측하기 어렵습니다

일급 컬렉션 적용

public class Cars {
    private final List<Car> cars;  // final로 재할당을 방지합니다

    public Cars(List<Car> cars) {
        this.cars = new ArrayList<>(cars);  // 방어적 복사를 수행합니다
    }

    // 수정 메서드가 없어 불변성이 보장됩니다
    public List<Car> getCars() {
        return Collections.unmodifiableList(cars);
    }
}

3. 🎯 상태와 행위의 응집도 높이기

문제 코드

// 여러 곳에 흩어진 자동차 관련 로직들입니다
public class CarUtils {
    public static int getMaxPosition(List<Car> cars) { ... }
}

public class WinnerCalculator {
    public List<Car> findWinners(List<Car> cars) { ... }
}

public class RacingGame {
    public void moveForward(List<Car> cars) { ... }
}

  • 자동차 관련 로직이 여러 클래스에 흩어져 있습니다
  • 새로운 기능 추가 시 위치 선정이 어렵습니다
  • 코드 재사용이 어려운 상태입니다

일급 컬렉션 적용

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

    public Cars move() {
        List<Car> movedCars = cars.stream()
            .map(Car::move)
            .collect(Collectors.toList());
        return new Cars(movedCars);
    }

    public List<Car> findWinners() {
        int maxPosition = getMaxPosition();
        return cars.stream()
            .filter(car -> car.getPosition() == maxPosition)
            .collect(Collectors.toList());
    }

    private int getMaxPosition() {
        return cars.stream()
            .mapToInt(Car::getPosition)
            .max()
            .orElse(0);
    }
}

생각 없이 일급 컬렉션을 써왔지만 이번 기회에 공부해봄으로써 일급 컬렉션의 본질을 알 수 있었습니다.

일급 컬렉션(First Class Collection)이라는 개념은 The ThoughtWorks Anthology라는 책의 6장에서 Jeff Bay가 처음 소개했습니다.