Java

책임연쇄패턴으로 분기문 처리하기

책임 연쇄 패턴이란 명령 객체와 일련의 처리 객체를 포함하는 디자인 패턴이다.
각각의 처리 객체는 명령 객체를 처리할 수 있는 연산의 집합으로 구성되고,
체인 안의 처리 객체가 다룰 수 없는 명령은 다음 처리 객체로 체이닝되어 넘겨진다.

 

주어진 요구사항은 다음과 같다.

요금 계산 방법

  • 기본운임(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