S E P H ' S

[행동 패턴] 책임 연쇄(Chain of Responsibility) 패턴 본문

Programing & Coding/Design Pattern

[행동 패턴] 책임 연쇄(Chain of Responsibility) 패턴

yoseph0310 2023. 3. 19. 15:24

책임 연쇄(Chain of Responsibility) 패턴

책임 연쇄 패턴은 하나 이상의 객체가 요청을 처리해야하고 메시지를 받을 수신측과 메시지를 송신할 송신측의 결합도를 낮추는 역할을 한다. 따라서 객체에게 책임을 할당하는데 유연성을 높일 수 있는 장점이 있지만 객체 연결이 잘 성립되지 않았다면 해당 요청이 무시될 수 있다.

 

책임 연쇄 패턴이란?

 

책임 연쇄 패턴은 메시지를 보내는 객체이를 받아 처리하는 객체들 간의 결합도를 낮추기 위한 패턴중에 하나다. 수신측에는 메시지를 처리할 객체들을 연결시켜 놓고 송신측에서 메시지를 수신측에게 전달하면 요청에 적합한 객체를 찾을때까지 연결된 객체들에게 메시지를 전달한다.

 

언제 사용할까?

 

  • 요청 객체와 처리 객체를 분리하거나 요청을 처리할 수 있는 객체가 여러개인데 하나의 객체에 요청을 보낼 때
  • 요청을 처리할 수 있는 객체가 여러개이고 이러한 처리를 하는 객체가 명시적이지 않을 때

 

구조

  • Handler : 요청을 처리하기 위한 객체들이 가질 인터페이스
  • ConcreteHandler : 요청 종류에 따라 자신이 처리 할 수 있는 부분을 구현한 객체
  • Client : 수신자에게 처리 요청
장단점

장점

  • 요청의 발신자, 수신자를 분리시킬 수 있다. 
  • 클라이언트는 처리 객체 내부 구조를 알 필요가 없다.
  • 처리 객체의 처리 순서를 변경하거나 추가, 삭제를 유연하게 할 수 있다.
  • 새로운 요청에 대해 객체 생성도 가능하다.

단점

  • 처리 객체들 내부에서 사이클이 발생할 수 있어 디버깅 및 테스트가 어렵다.
  • 요청을 처리할 객체가 체인 내부에서 어느 위치에 있을지는 동적으로 변경되어 시간 예측이 어렵다.
  • 구현 클래스가 많이 필요하고 유지보수성이 높아질 수 있다.

 

예시

 

돈을 뽑을 때 사용하는 ATM 기기를 예시로 들어볼 것이다. '돈 인출'이라는 하나의 요청에 대해 ATM기는 인출하려는 총액을 받고 금액에 따라 10만원권, 5만원권, 1만원권을 인출해야 한다. 하나의 요청(돈 인출)을 하나의 클래스(ATM)가 받아 각각의 클래스(10만원권, 5만원권, 1만원권)들이 요청을 처리하는 방식으로 구현해보겠다.

 

먼저 체인 인터페이스와 ATM 클래스를 작성한다.

 

public class ATM {
    private int amount;

    public int getAmount() {
        return amount;
    }

    public ATM(int amount) {
        this.amount = amount;
    }
}

 

public interface WithdrawChain {
    void setNextChain(WithdrawChain withdrawChain);
    void withdraw(ATM atm);
}

 

그 다음으로는 인출 요청에 대해 각각의 방식으로 요청을 처리할 체인 클래스들을 구현한다. 10만원권, 5만원권, 1만원권 '만'을 처리할 클래스들이다.

 

public class Withdraw100000Won implements WithdrawChain {

    private WithdrawChain withdrawChain;

    @Override
    public void setNextChain(WithdrawChain withdrawChain) {
        this.withdrawChain = withdrawChain;
    }

    @Override
    public void withdraw(ATM atm) {
        // 총액이 10만원보다 크거나 같다면 10만원 체인에서 처리 후 남은 금액은 다음 체인에서 처리
        if (atm.getAmount() >= 100000) {
            int cashCnt = atm.getAmount() / 100000;
            int remain = atm.getAmount() % 100000;

            System.out.println("*** 10만원권 " + cashCnt + " 장이 인출 되었습니다. ***");

            if (remain != 0) this.withdrawChain.withdraw(new ATM(remain));
        }
        // 10만원보다 작다면 현재 금액 처리를 다음 체인에서 처리
        else {
            this.withdrawChain.withdraw(atm);
        }
    }
}

 

public class Withdraw50000Won implements WithdrawChain {
    private WithdrawChain withdrawChain;

    @Override
    public void setNextChain(WithdrawChain withdrawChain) {
        this.withdrawChain = withdrawChain;
    }

    @Override
    public void withdraw(ATM atm) {
        // 총액이 5만원보다 크거나 같다면 10만원 체인에서 처리 후 남은 금액은 다음 체인에서 처리
        if (atm.getAmount() >= 50000) {
            int cashCnt = atm.getAmount() / 50000;
            int remain = atm.getAmount() % 50000;

            System.out.println("*** 5만원권 " + cashCnt + " 장이 인출 되었습니다. ***");

            if (remain != 0) this.withdrawChain.withdraw(new ATM(remain));
        }
        // 5만원보다 작다면 현재 금액 처리를 다음 체인에서 처리
        else {
            this.withdrawChain.withdraw(atm);
        }
    }
}

 

public class Withdraw10000Won implements WithdrawChain {
    private WithdrawChain withdrawChain;

    @Override
    public void setNextChain(WithdrawChain withdrawChain) {
        this.withdrawChain = withdrawChain;
    }

    @Override
    public void withdraw(ATM atm) {
        // 총액이 1만원보다 크거나 같다면 10만원 체인에서 처리 후 남은 금액은 다음 체인에서 처리
        if (atm.getAmount() >= 10000) {
            int cashCnt = atm.getAmount() / 10000;
            int remain = atm.getAmount() % 10000;

            System.out.println("*** 1만원권 " + cashCnt + " 장이 인출 되었습니다. ***");

            if (remain != 0) this.withdrawChain.withdraw(new ATM(remain));
        }
        // 1만원보다 작다면 인출할 수 없다는 메시지를 출력
        else {
            System.out.println("*** 인출할 수 없는 금액입니다. 1만원 이상의 금액만 인출이 가능합니다. ***");
        }
    }
}

 

위의 예시와 같은 경우는 반드시 단위가 큰 금액부터 처리하도록 체인의 순서를 정해야 한다.

작은 금액부터 처리한다면 다음 체인들이 필요없어져 버린다.

 

테스트
public class ChainOfResponsibilityTest {
    public static void main(String[] args) {
        WithdrawChain withdrawChain100000Won = new Withdraw100000Won();
        WithdrawChain withdrawChain50000Won = new Withdraw50000Won();
        WithdrawChain withdrawChain10000Won = new Withdraw10000Won();

        withdrawChain100000Won.setNextChain(withdrawChain50000Won);
        withdrawChain50000Won.setNextChain(withdrawChain10000Won);

        System.out.println("*** 인출할 금액을 입력하십시오. ***");
        Scanner sc = new Scanner(System.in);

        int money = sc.nextInt();

        ATM atm = new ATM(money);
        withdrawChain100000Won.withdraw(atm);

        System.out.println("---------- 인출 처리 ----------");
    }
}

입력한 금액에서 각각의 체인에서 처리하여 알맞게 인출한 것을 확인할 수 있다. 이렇게 하나의 요청을 받아 여러개의 객체로 처리 로직을 분리하여 처리 객체들간의 결합도를 낮출 수 있는 책임 연쇄 패턴에 대해서 알아봤다. 

 

 


예제 참조 : https://donghyeon.dev/design%20pattern/2020/05/11/Chain-of-Responsibility-%ED%8C%A8%ED%84%B4/