[디자인패턴] 행동패턴(11) - Chain of Responsibility Pattern (책임 연쇄 패턴)

Study/Software Architecture · 2023. 6. 12. 23:13
반응형

[디자인패턴] 행동패턴(11) - Chain of Responsibility Pattern (책임 연쇄 패턴)은 요청을 처리할 수 있는 객체들을 체인(Chain) 형태로 연결해놓고, 클라이언트가 요청을 보냈을 때 그 요청을 처리할 수 있는 적합한 객체가 자동으로 처리하도록 하는 디자인 패턴입니다. 예를 들어, 예외 처리나 로깅(log) 시스템에서 체인을 따라가며 책임을 분산하는 방식으로 활용할 수 있습니다.

이번 글에서는 Chain of Responsibility 패턴의 개념, 실제 사용 예시, 장단점을 살펴보고, Python 예제 코드를 통해 GoF 디자인 패턴의 클래스 다이어그램과 유사한 구조를 함께 확인해보겠습니다.

 

1. 개요

Chain of Responsibility 패턴에서는 Handler(또는 추상 클래스)가 기본 틀을 제공하며, ConcreteHandler들이 실제로 요청을 처리할 수 있는 기능을 각각 구현합니다. 체인 상에서 한 객체가 처리할 수 없으면 다음 객체에게 넘기는 식으로, 동적으로 책임을 떠넘길 수 있는 구조가 만들어집니다.

 


2. 예시

예시로, 로그 레벨에 따라 다른 로거(Logger)가 처리를 담당하는 시나리오를 생각해볼게요. INFO, DEBUG, ERROR 같은 레벨이 있을 때, INFO 처리기가 처리할 수 없다면 체인을 타고 DEBUG 처리기로 넘어가고, 그래도 처리되지 않으면 ERROR 처리기로 전달하게 됩니다.

# Chain of Responsibility in Python (GoF 유사 구조)

class LogLevel:
    INFO = 1
    DEBUG = 2
    ERROR = 3

# Handler (추상 클래스 역할)
class Logger:
    def __init__(self, next_logger=None):
        self._next_logger = next_logger

    def set_next(self, next_logger):
        self._next_logger = next_logger

    def log(self, level, message):
        # 자식 클래스에서 오버라이딩하여 처리함
        if self._next_logger:
            self._next_logger.log(level, message)

# ConcreteHandler 1
class InfoLogger(Logger):
    def log(self, level, message):
        if level == LogLevel.INFO:
            print(f"[Python] [INFO] {message}")
        else:
            super().log(level, message)

# ConcreteHandler 2
class DebugLogger(Logger):
    def log(self, level, message):
        if level == LogLevel.DEBUG:
            print(f"[Python] [DEBUG] {message}")
        else:
            super().log(level, message)

# ConcreteHandler 3
class ErrorLogger(Logger):
    def log(self, level, message):
        if level == LogLevel.ERROR:
            print(f"[Python] [ERROR] {message}")
        else:
            super().log(level, message)

if __name__ == "__main__":
    # 체인 생성
    info_logger = InfoLogger()
    debug_logger = DebugLogger()
    error_logger = ErrorLogger()

    info_logger.set_next(debug_logger)
    debug_logger.set_next(error_logger)

    # 다양한 로그 레벨 메시지 전송
    info_logger.log(LogLevel.INFO, "Application has started.")
    info_logger.log(LogLevel.DEBUG, "Debugging user data.")
    info_logger.log(LogLevel.ERROR, "Something went terribly wrong!")

위 코드에서 Logger 클래스는 추상 클래스(또는 기반 클래스) 역할을 맡습니다. 실제 처리 로직은 InfoLogger, DebugLogger, ErrorLogger와 같은 ConcreteHandler들이 분담하여 수행합니다. set_next를 통해 체인을 동적으로 구성할 수 있고, 로직이 확장되어도 체인 구성만 바꾸면 되므로 유연성이 높아집니다.

 

 

3. 장점

  1. 결합도 감소: 클라이언트는 어느 객체가 처리하는지 알 필요가 없습니다. 요청 처리 로직이 여러 객체로 나뉘어져도 체인을 통해 유연하게 관리됩니다.
  2. 확장성: 새로운 처리자가 필요하면 체인 상에 쉽게 추가할 수 있어 구조가 유연합니다.

 

4. 단점

  1. 디버깅이 어려울 수 있음: 어디서 요청이 처리되는지 추적하기가 까다로울 수 있습니다.
  2. 체인 설정 실수: 체인이 잘못 연결되면 원하는 처리자가 호출되지 않을 가능성이 있습니다.

 

5. 결론

Chain of Responsibility 패턴은 요청이 어느 객체에 의해 처리될지 유연하게 결정할 수 있다는 점에서 매우 편리합니다. 책임이 여러 객체로 분산되고, 새로운 객체를 체인에 연결하기만 하면 되므로 확장성과 유지보수성이 뛰어납니다. 다만, 체인이 길어질 경우 디버깅이 어려워질 수 있으니 설계 시 주의가 필요합니다.

 

반응형