일급컬렉션의 장점은 컬렉션의 불변을 보장? i am different 하게 생각해요
글을 작성한 이유 & 일급 컬렉션을 선택한 이유
우아한 테크코스 2주 차 미션을 수행하면서 일급 컬렉션을 사용해 글을 작성하면서 완벽히 이해하려고 합니다.
원래는 MVC 패턴을 통해 Service(Model) 레이어에서 Car의 상태에 관련된 로직을 작성하려고 했으나, 만약 Service에서 관리해야 하는 대상이 Car 뿐만 아니라 Airplain, Bus 등 다양해지면 어떻게 될까?라는 생각에서 일급 컬렉션을 적용하게 되었다.
일급 컬렉션이란?
Collection을 Wrapping하면서, Wrapping 한 Collection 외 다른 멤버 변수가 없는 상태를 일급 컬렉션이라 한다.
public class Car {
private String name;
private int location;
}
2차 미션을 수행할 때 생성한 Car 클래스이다.
public class Cars {
private List<Car> carList;
}
Cars처럼 Collection을 Wrapping 하며 그 외에 다른 멤버 변수가 없는 상태를 일급 컬렉션이라 한다.
왜 일급 컬렉션을 선택했을까?
Service 클래스에서 비즈니스 로직을 담당 할 때 생기는 문제점
public class RacingService {
public List<String> stringToList(String input) {
return Arrays.asList(input.split(","));
}
public void moveCars(List<Car> cars) {
for (Car car : cars) {
int randomNumber = createRandomNumber();
if (checkNumberOverThree(randomNumber)) {
car.moveCar();
}
}
}
public GameResultDto determineWinners(List<Car> cars) {
int maxLocation = getMaxLocation(cars);
List<String> winners = getWinners(cars, maxLocation);
return new GameResultDto(winners);
}
private int getMaxLocation(List<Car> cars) {
return cars.stream()
.mapToInt(Car::getLocation)
.max()
.orElseThrow(ExceptionHandler::throwIfCarNotFound);
}
private List<String> getWinners(List<Car> cars, int maxLocation) {
return cars.stream()
.filter(car -> car.isSameLocation(maxLocation))
.map(Car::getName)
.collect(Collectors.toList());
}
public List<Car> createCarsFromNames(List<String> carNames) {
return carNames.stream()
.map(Car::new)
.collect(Collectors.toList());
}
}
위 코드는 Service 클래스에서 자동차를 생성하고, 움직임을 제어하며, 우승자를 결정하는 코드입니다.
지금까지는 별 문제가 없어 보일 수 있습니다.
만약 자동차 경주에서 비행기 경주와 경마가 추가 된다면?
public void moveCars(List<Car> cars) {
for (Car car : cars) {
int randomNumber = createRandomNumber();
if (checkNumberOverThree(randomNumber)) {
car.moveCar();
}
}
}
public void moveAirplanes(List<Airplane> airplanes) {
for (Airplane airplane : airplanes) {
int randomNumber = createRandomNumber();
if (checkNumberOverThree(randomNumber)) {
airplane.moveAirPlane();
}
}
}
public void moveHorses(List<Horse> horses) {
for (Horse horses : horses) {
int randomNumber = createRandomNumber();
if (checkNumberOverThree(randomNumber)) {
horses.moveAirPlane();
}
}
차, 비행기, 말을 움직이는 로직만 작성해 봤는데 벌써부터 불편하죠...
Service클래스에서 저렇게 작업을 하면 클래스에 엄청난 부담을 줄 뿐만 아니라, 중복 코드가 많이 생깁니다.
중복 코드를 가장 싫어하는 1인으로써 생각만 해도 소름이 끼칠 정도로 싫더라고요,,,
일급 컬렉션을 사용하면?
일급 컬렉션을 사용하게 된다면 차의 상태와 비즈니스 로직은 Collection이 포함된 클래스인 "Cars"에서 정의할 것이다. 비행기와 말 또한 상태와 비즈니스 로직을 각각의 Collection이 포함된 클래스에서 정의할 것이다.
일급 컬렉션을 생성한 뒤, 비즈니스 로직을 사용하는 곳에서 호출을 하면 중복 코드가 줄어드며, 클래스의 부담이 줄어든다.
코드를 보면서 설명드리겠습니다.
public class Cars {
private List<Car> carList;
public Cars(List<String> carNames) {
this.carList = carNames.stream()
.map(Car::new)
.collect(Collectors.toList());
}
}
public class Airplanes {
private List<AirPlane> airplaneList;
public Airplanes(List<String> airplaneNames) {
this.airplaneList = airplaneNames.stream()
.map(AirPlane::new)
.collect(Collectors.toList());
}
}
public class Horses {
private List<Horse> horseList;
public Horses(List<String> horseNames) {
this.horseList = horseNames.stream()
.map(Horse::new)
.collect(Collectors.toList());
}
}
이후, 비즈니스 로직이 사용되는 곳에서 호출하면 코드가 훨씬 간단해지며, 클래스의 부담이 줄어듭니다.
Cars cars = new Cars(carNames);
Airplanes airplanes = new Airplanes(airplaneNames);
Horses horses = new Horses(horseNames);
결론
RacingService에서 처리하던 비즈니스 로직을 각각의 Collection을 Wrapping 한 클래스에서 처리한 후, 호출하면 중복 코드가 줄어드며 Class에 부담이 많이 줄어든다.
일급컬렉션의 장점은 컬렉션의 불변을 보장? i am different 하게 생각해요
일급 컬렉션을 코드에 적용시키기 전에 정말 많은 블로그와 문서를 보았다.
이때 모두들 공통적으로 하는 말이 "일급 컬렉션의 장점은 Collection의 불변성을 보장합니다."였다.
하지만 나는 다르게 생각한다.
1. setter 아니어도 Collection에 변화를 줄 수 있다.
public void setCars(List<Car> carList) {
this.cars = carList;
}
public Cars(List<Car> carList) {
this.cars = carList;
}
똑같은 기능을 하는 두 가지의 코드를 가져와봤다.
첫 번째는 setter의 매개변수를 통해 carList를 받은 뒤, 멤버 변수에 할당하였다.
두 번째는 생성자의 매개변수를 통해 carList를 받은 뒤, 멤버 변수에 할당하였다.
매개변수를 통해 받은 뒤, 멤버 변수에 할당하면 되기 때문에 방법은 훨씬 많을 것이다.
setter을 사용하지 않았다고 해서 "불변성을 잘 지켰네"라는 생각은 하지 않아도 된다.
2. Agile 고수들의 한마디
Agile의 고수들이 모인 ThoughtWorks 회사에서 출판한 thoughtworks-anthology 책의 6장을 인용해서 설명하겠다.
1. Collection을 포함하고 있는 클래스는 다른 멤버 변수를 가지고 있으면 안 된다.
any class that contains a collection should contain no other member variables.
2. 각 컬렉션은 고유 클래스로 포장되므로 컬렉션과 관련된 행동은 이제 집(클래스)이 있다.
조금 더 쉽게 설명하자면 컬렉션과 관련된 행동들을 이제 사용하는 곳(Service, Controller.. etc)에서 정의하지 말고 컬렉션을 포함하고 있는 클래스에서 정의하라는 말이다.
Each collection gets wrapped in its own class, so now behaviors related to the collection have a home.
고수들의 한마디를 보면 일급 클래스는 다른 멤버 변수를 가지고 있으면 안 되고, 컬렉션과 관련된 행동은 컬렉션을 담고 있는 클래스에서 정의하라고 한다.
아무리 찾아보아도 "일급 컬렉션은 불변성을 보장한다"라는 말은 없다.
참고
https://tecoble.techcourse.co.kr/post/2020-05-08-First-Class-Collection/