디자인 패턴 - 책임연쇄

책임연쇄(Chain of Responsibility) 패턴에 대해서 알아보겠습니다.

written by tiaz0128

책임연쇄(Chain of Responsibility) 패턴


책임 연쇄 패턴(Chain of Responsibility)은 행동 디자인 패턴 중 하나로, 객체들의 체인을 따라 요청을 전달하고 처리하는 방식으로 작동합니다. 대표적으로 아래와 같은 상황에 많이 사용되는 패턴입니다.

사용 사례

보고서 승인


여기서는 ‘보고서 승인 처리’를 구현해보면서 책임연쇄 패턴에 대해 알아보겠습니다.

상황

사원이 작성한 보고서는 팀장-사장을 통해 최종 승인됩니다. 굳이 사장의 승인까지 필요없는 보고서는 팀장이 최종 승인하는 경우도 있습니다.

보고서 승인

보고서 클래스 (Report)

우선 보고서 클래스를 만들겠습니다. 보고서는 내용(content)과 최종 승인에 필요한 승인 레벨(required_approval_level) 속성을 가집니다.

from enum import Enum, auto

class ApprovalLevel(Enum):
    TEAM_LEAD = auto()
    PRESIDENT = auto()

class Report:
    def __init__(self, content, required_approval_level: ApprovalLevel):
        self.content = content
        self.required_approval_level = required_approval_level

결재자 인터페이스 (Approver)

요청을 처리하는 핸들러(handler) 인터페이스를 정의합니다. 결재자는 승인 레벨과 다음 결재자(next_approver)를 가집니다. 이 next 속성값 체인을 통해 보고서를 단계별로 승인 처리하는 것이죠.

from abc import ABC, abstractmethod
from enum import Enum, auto

class Approver(ABC):
    def __init__(self, level: ApprovalLevel):
        self.level = level
        self.next_approver = None

    def set_next(self, approver):
        self.next_approver = approver
        return approver

    def process_report(self, report):
        is_approved = False
        if self.level.value <= report.required_approval_level.value:
            is_approved = self.approve(report)

        if self.next_approver and is_approved:
            return self.next_approver.process_report(report)
        return False

    @abstractmethod
    def approve(self, report):
        pass

각 결재자

위에 만든 핸들러 인터페이스를 실제 구현하는 구체적 핸들러(Concrete Handler)인 각각의 결재자 클래스를 만듭니다. 각 결재자의 approve를 구현해줍니다.

from random import choice

class TeamLead(Approver):
    def __init__(self):
        super().__init__(ApprovalLevel.TEAM_LEAD)

    def approve(self, report):
        is_approved = choice([True, False])
        approve_msg = '승인' if is_approved else '반려'

        print(f'팀장이 ~{report.content}~ 보고서를 "{approve_msg}"했습니다')
        return is_approved

class President(Approver):
    def __init__(self):
        super().__init__(ApprovalLevel.PRESIDENT)

    def approve(self, report):
        is_approved = choice([True, False])
        approve_msg = '종결' if is_approved else '반려'

        print(f'사장이 ~{report.content}~ 보고서를 "{approve_msg}"했습니다')

        return is_approved

체인 생성

이제 만들어둔 핸들러 객체들의 체인을 set_next를 통해 만들어 줍니다. 여기서 중요한것은 체인을 연결하는 순서입니다. 먼저 팀장이 보고서를 검토하고 다음에 사장이 결재하는 것 처럼, 어떤 객체에서 다음 객체를 연결할지 신중하게 결정해야 합니다.

필요에 따라서 다른 결재자를 얼마든 만들어 추가하고 체인으로 연결 할 수 있습니다.

def setup_approval_chain():
    team_lead = TeamLead()
    president = President()

    team_lead.set_next(president)

    return team_lead

클라이언트 코드

보고서 객체를 만들어서 전달합니다. 보고서 객체를 체인 통해 다음 결자재 객체에 전달해서 승인 절차를 수행합니다. 다음 결자재가 없으면 체인 종료합니다.

핸들러에서 처리하는 로직에 따라서 다양하게 구현이 가능합니다. ‘연간 전략 계획’ 보고서 같은 경우, process_report에서 구현에 따라 팀장이 반려하면 사장에게 보고서가 전달되지 않고 그대로 체인은 종료니다.

approval_chain = setup_approval_chain()

report1 = Report("일일 업무 보고", ApprovalLevel.TEAM_LEAD)
report3 = Report("연간 전략 계획", ApprovalLevel.PRESIDENT)

approval_chain.process_report(report1)
approval_chain.process_report(report2)
팀장이 ~일일 업무 보고~ 보고서를 "반려"했습니다

팀장이 ~연간 전략 계획~ 보고서를 "승인"했습니다
사장이 ~연간 전략 계획~ 보고서를 "종결"했습니다

클래스 다이어그램


%%{
  init: {
    'theme': 'base',
    'themeVariables': {
      'primaryColor': '#2a3844',
      'lineColor': '#fff',
      'primaryTextColor': '#fff',
      'tertiaryColor': '#fff'
    }
  }
}%%

classDiagram
    direction LR

    class Approver {
        <<abstract>>

        -level: ApprovalLevel
        -next_approver: Approver
        +set_next(approver: Approver)
        +approve(report: Report)*
    }

    class TeamLead {
        +approve(report: Report)
    }
    class President {
        +approve(report: Report)
    }

    Approver <|-- TeamLead
    Approver <|-- President

    TeamLead o-- President

정리


구조 정리

작동 방식

각 핸들러는 다음 핸들러에 대한 참조를 가집니다. 요청이 들어오면 각 핸들러는 그 요청을 처리할 수 있는지 결정합니다. 처리를 수행하고 다음 핸들러로 요청을 전달합니다. 체인의 끝에 도달하거나 요청이 처리될 때까지 이 과정이 계속됩니다.

구현 시 고려사항

더 해볼 것


책임연쇄(Chain of Responsibility) 패턴을 통해서 또 다른 다양한 문제를 구현해보는 것도 좋을 듯합니다. 😊

python 디자인패턴 책임연쇄

tiaz0128

Eat Sleep Coding.

Never Never Giveup.

💫 Python  |  BackEnd  |  Cloud Architect  |  DevOps