설계패턴 21. Chain of Responsibility Pattern

LSDrug·2024년 6월 13일

설계패턴(完)

목록 보기
21/26

1. 들어가기 전에...

어떤 서류를 받으러 회사에 갔다고 하자. 회사의 안내센터에서 물어보니 영업부로 가라고 하고, 영업부에 갔더니 그 서류는 고객관리부에서 취급한다고 하고, 다시 고객관리부에 갔더니 그 서류는 총무부에서 받을 수 있다고 했다.

이런 식으로 담당 부서의 담당자를 찾을 때까지 자신의 요구가 차례대로 넘겨지는 것이 "책임 떠넘기기"라고 볼 수 있다.

2. 정의

복수의 오브젝트를 사슬처럼 연결해두면, 그 오브젝트의 사슬을 차례대로 돌아다니면서 목적한 오브젝트(객체)를 결정하는 방법

responsibility 개념을 갖는 모듈들에 체인을 만드는 패턴.

3. 개념 확장

등장인물

  1. Handler : 요구를 처리하는 인터페이스를 결정하는 역할. '다음 담당자'를 준비해 두고 자신이 처리할 수 없는 요구가 나오면 그 담당자에게 떠넘기기를 한다. 물론 '다음 담당자'도 열심히 떠넘긴다.

  2. ConcreteHandler : 요구를 처리하는 구체적인 역할을 한다.

  3. Client : 최초의 ConcreteHandler 역할에 요구하는 일을 한다.

요구하는 사람과 요구를 처리하는 사람의 유연한 연결

  • Client는 최초로 만나는 담당자에게 요구하며, 뒷일은 사슬 안으로 그 요구가 전달되어 적절한 다른 담당자에 의해 처리된다.

  • 만약 Chain of Responsibility를 사용하지 않으면 '이 요구는 이 담당자가 처리해야 한다.'라는 정보를 누군가 중앙집권적으로 가지고 이어야 한다.

  • 이러한 정보를 '요구하는 사람'에게 갖게 하는 것은 현명한 방법이 아니다.
    -> 요구하는 사람이 담당자들의 역할 분담까지 자세하게 알아야 한다면 부품으로써의 독립성이 훼손되기 때문이다.

동적으로 사슬의 형태를 변경

요구를 처리하는 ConcreteHandler 역할의 오브젝트 관계가 동적으로 변화하는 상황이 있을 때, ConcreteHandler 역할을 재편할 수 있다.

만약, CoR 패턴을 사용하지 않고 프로그램 안에 '이 요구라면 이 담당자'라는 대응 관계가 고정적으로 기술되어 있으면 프로그램 실행 중에 처리자를 변경하기 어렵다.

자신의 일에 집중

떠넘기기는 부정적 의미가 강하지만, 각 오브젝트는 자신의 일에 집중할 수 있다는 의미가 되기도 한다.

각각의 ConcreteHandler 역할들이 사용해야 할 처리에 대한 것들이 고유한 내용으로 집중할 수 있게 된다.

떠넘기기로 인하여 처리가 지연되지 않을까?

해당 구조는 틀림없이 유연성은 높을지 모르지만 처리가 지연될 것 같다.

누가 요구를 처리할지 미리 정해져 있고 그 담당자가 바로 처리하는 경우와 비교하면 지연되는 부분도 있을 것이다...
즉, 일종의 트레이드 오프로 봐야 한다.

요구자와 처리자의 관계가 고정적이고 처리 속도가 상당히 중요한 경우 CoR 패턴을 굳이 사용할 이유가 없을 것이다.

4. 예시

오류를 처리하는 코드를 작성해보자

class Handler:
    def __init__(self):
        self.next_handler = None

    def setNext(self, handler):
        self.next_handler = handler

    def handle(self, req):
        if self.next_handler:
            return self.next_handler.handle(req)
        print("All handlers failed")
        return None

class CashHandler(Handler):
    def handle(self, req):
        if req['method'] == 'cash':
            print(f"processing cash ({req['amount']} won)")
        else:
            print("CashHandler cannot process")
            super().handle(req)

class CreditCardHandler(Handler):
    def handle(self, req):
        if req['method'] == 'creditcard':
            print(f"processing creditcard ({req['amount']} won)")
        else:
            print("CreditCardHandler cannot process")
            super().handle(req)

class DebitCardHandler(Handler):
    def handle(self, req):
        if req['method'] == 'debitcard':
            print(f"processing debitcard ({req['amount']} won)")
        else:
            print("DebitcardHandler cannot process")
            super().handle(req)

cash_handler = CashHandler()
creditcard_handler = CreditCardHandler()
debitcard_handler = DebitCardHandler()

cash_handler.setNext(creditcard_handler)
creditcard_handler.setNext(debitcard_handler)

payment = {
    "method": "cash",
    "amount": 10000
}
cash_handler.handle(payment)

payment = {
    "method": "debitcard",
    "amount": 10000
}
cash_handler.handle(payment)

payment = {
    "method": "paypal",
    "amount": 10000
}
cash_handler.handle(payment)

profile
마약같은 코딩, 마약같은 코딩러

0개의 댓글