우아한 테크코스의 프리코스(자동차 경주)를 진행하며 생겼던 고민과 해결방법에 대해 정리하려 한다.
고민. getter의 활용을 객체 간 대화로 해결하기
변경 전
경주를 진행하는 Car
객체들을 관리하는 Cars
객체이다.
기존의 로직은 현재 가장 멀리 이동한 자동차의 포지션, 선두 자동차를 찾기위해 getter
를 사용했다.
public class Cars {
private final List<Car> cars;
public List<String> findTopCars() {
int topPosition = this.findTopPosition();
return cars.stream()
.filter(car -> car.isSamePosition(topPosition))
.map(Car::getName)
.toList();
}
private int findTopPositionCar() {
int maxPosition = 0;
cars.forEach(car -> {
int position = car.getPosition();
maxPosition = Math.max(position);
});
return maxPosition;
}
}
여기서 문제는 객체 간의 대화로 문제를 해결한 것이 아닌, Cars
로 비교에 대한 책임을 위임했다는 것이다. 이는 Cars
가 Car
의 getPosition()
메서드와 매우 강한 의존을 하게 된다.
지금은 getter로 반환하는 변수가 원시타입이기 때문에 외부에서의 변경에 안전하긴 하다.
하지만 position이 컬렉션 혹은 내부 프로퍼티에 대한 변경이 허용된 참조타입 이라면?
외부에서의 변경을 완벽하게 배제할 순 없을 것이다.
참조 타입을 딥카피해서 반환하기엔 너무 높은 cost가 요구된다.
따라서 이를 객체 간의 대화로 문제를 해결하려 한다.
변경 후
Car
객체가 자신과 같은 클래스를 인자로 넘겨받고, 비교를 진행한 후 결과를 넘겨주도록 변경했다.
public class Car implements Comparable<Car> {
private int position;
public boolean isSamePosition(Car other) {
return this.position == other.position;
}
@Override
public int compareTo(Car other) {
return this.position - other.position;
}
}
Cars
객체에서도 getter
로 인자를 넘겨받아 비교하는 것이 아닌
객체가 로직을 수행하도록 변경하였다.
public class Cars {
private final List<Car> cars;
public List<String> findTopCars() {
Car topPositionCar = this.findTopPositionCar();
return cars.stream()
.filter(topPositionCar::isSamePosition)
.map(Car::getName)
.toList();
}
private Car findTopPositionCar() {
return cars.stream()
.max(Car::compareTo)
.orElseThrow(() -> new IllegalArgumentException("Car 리스트가 비어있음."));
}
}
결론
기존에는 Car
의 getter
와 Cars
는 강한 의존성을 가지고 있었다.
리팩터링 진행 후 비교의 책임을 Car
에게 위임하면서 Car
가 자기 자신과 의존을 맺게 되었고, 이는 Cars
와 Car
의 의존성을 낮추는 데 도움이 되었다.
오해하고 있었던 것은 private 접근제한자가 같은 인스턴스에 한해 접근이 가능한 줄 알았다는 것.
그래서 getter를 통해 외부에서 비교를 했던 것인데 같은 인스턴스가 아닌 같은 클래스면 접근이 가능했었다.. 잘못된 걸 바로 고칠 수 있었다.
getter의 사용을 무조건적으로 금지하는 것은 옳지 않은 것 같다.
출력을 위한 순수 프로퍼티 값을 위해서는 getter의 사용이 필수불가결하다.
따라서 getter를 적재적소에 사용하는 것을 더 연습해야겠다.
'JAVA' 카테고리의 다른 글
GC 짚고 넘어가기 (1) | 2023.12.29 |
---|---|
[JAVA 객체지향] 비즈니스 로직과 View의 의존성 배제하기 (0) | 2023.11.02 |
[JAVA] 접근자 메서드(getter/setter)를 왜 써야할까? (0) | 2023.05.25 |
[Java] DIP - 의존성? 의존성 역전? (0) | 2023.04.26 |
[JAVA] 컬렉션 프레임워크 (1) (0) | 2023.04.12 |