책임 연쇄 패턴이란 명령 객체와 일련의 처리 객체를 포함하는 디자인 패턴이다.
각각의 처리 객체는 명령 객체를 처리할 수 있는 연산의 집합으로 구성되고,
체인 안의 처리 객체가 다룰 수 없는 명령은 다음 처리 객체로 체이닝되어 넘겨진다.
주어진 요구사항은 다음과 같다.
요금 계산 방법
- 기본운임(10㎞ 이내) : 기본운임 1,250원
- 이용 거리초과 시 추가운임 부과
- 10km초과∼50km까지(5km마다 100원)
- 50km초과 시 (8km마다 100원)
9km = 1250원
12km = 10km + 2km = 1350원
16km = 10km + 6km = 1450원
75km = 10km + 40km + 25km = 2450원
이 경우, 분기문으로 처리한다면 다음과 같이 처리할 수 있다.
if (distance < 10) {
return 1250;
}
if (distance < 50) {
return calculateOverFare(distance, 5);
}
return calculateOverFare(distance, 8);
private int calculateOverFare(int distance, int km) {
return (int) ((Math.ceil((distance - 1) / km) + 1) * 100);
}
아주 마음에 들지 않는다.
이를 개선하기 위해 우리가 이 요구사항에 대해 책임 연쇄 패턴을 적용할 수 있는 이유는 다음과 같다.
1. 특정 기준에 따라 요청을 수행할 수도 있고, 수행하지 못할 수도 있다.
2. 그 기준을 넘으면 다음 객체에게 요청할 수 있다.
코드를 개선해보자
먼저 상위 인터페이스인 Chain을 만든다. 우리는 요금 관련 기능이므로 FareChain이라 명명해보았다.
체인들이 공통적으로 갖는 기능은 다음과 같다.
1. 다음 체인을 유연하게 설정할 수 있다.
2. 현재 체인에서 해당하는 요금을 계산한다.
3. 현재 체인에서 처리할 수 없는 경우 다음 체인에게 요금 계산을 넘긴다.
public interface FareChain {
void setNextChainByChain(FareChain fareChain);
int calculateFare(int distance);
}
우선, 10km 이하 > 10km 초과 및 50km 이하 > 50km 초과 순으로 넘겨주는 방식으로 책임연쇄패턴을 구현하였다.
public class FirstFare implements FareChain {
private final int THRESHOLD = 10;
FareChain nextFareChain;
@Override
public void setNextChainByChain(FareChain nextFareChain) {
this.nextFareChain = nextFareChain;
}
@Override
public int calculateFare(int distance) {
if (distance > THRESHOLD) {
return calculateFare(THRESHOLD)
+ nextFareChain.calculateFare(distance);
}
return 1250;
}
}
public class SecondFare implements FareChain {
private static final int KM = 5;
private static final int THRESHOLD = 50;
private static final int BASIC_THRESHOLD = 10;
private FareChain nextFareChain;
@Override
public void setNextChainByChain(FareChain nextFareChain) {
this.nextFareChain = nextFareChain;
}
@Override
public int calculateFare(int distance) {
if (distance > THRESHOLD) {
return calculateFare(THRESHOLD)
+ nextFareChain.calculateFare(distance);
}
return (int) ((Math.ceil((distance - BASIC_THRESHOLD - 1) / KM) + 1) * 100);
}
}
public class ThirdFare implements FareChain {
private static final int KM = 8;
private static final int THRESHOLD = 50;
@Override
public void setNextChainByChain(FareChain nextFareChain) {
}
@Override
public int calculateFare(int distance) {
return (int) ((Math.ceil((distance - THRESHOLD - 1) / KM) + 1) * 100);
}
}
각 체인에서 입력값인 distance에 따른 요금 계산을 수행하는데, 분기를 넘어가게 되면 다음 체인에게 그 계산을 요청하고 그 결과값을 누적해서 리턴한다. 누적해서 리턴하는 이유는 75km와 같은 경우 10km + 40km + 25km와 같이 이전 체인을 꽉 채운 요금값을 받아내고 있기 때문이다.
위 코드를 좀 더 개선한다면 공통된 계산 로직을 abstract class로 빼내어 인자를 넘겨줌으로서 계산하도록 할 수 있을 것이다!
장점
- 요청을 보내는 객체와 이를 처리하는 객체간의 결합도를 느슨하게 할 수 있으며 여러 객체에게 처리 기회를 줄 수 있다. 추가로 분기를 해결할 수 있다.
참고 자료
- https://k0102575.github.io/articles/2020-02/chain-of-responsibility-pattern