본문 바로가기
Thinking/Concept

책임 연쇄 패턴(Chain of Responsibility Pattern) 이해하기

by Dev_카페인 2024. 11. 16.
반응형

책임 연쇄 패턴(Chain of Responsibility Pattern) 이해하기

책임 연쇄 패턴은 요청을 처리할 수 있는 객체들이 연쇄적으로 연결되어, 요청이 처리될 때까지 객체를 순차적으로 전달하는 행동 디자인 패턴입니다. 이 패턴은 요청을 명확한 수신자 없이도 여러 객체가 처리할 수 있도록 하며, 처리 과정을 동적으로 변경하거나 확장할 수 있습니다.

책임 연쇄 패턴은 요청을 처리할 수 있는 다수의 객체들 중 하나가 처리하도록 설계되어 있으며, 처리하지 못하면 다음 객체로 전달됩니다. 이번 포스트에서는 책임 연쇄 패턴의 개념, 구조, 구현 방법, 장단점, 사용 사례 등을 구체적인 예제와 함께 살펴보겠습니다.

책임 연쇄 패턴

책임 연쇄 패턴이란?

책임 연쇄 패턴은 요청을 처리할 수 있는 객체들이 연결된 체인 형태로 구성되어, 요청이 체인을 따라 전달되며 적합한 객체가 이를 처리하는 패턴입니다.

이 패턴의 주요 목표는 요청을 명확한 수신자 없이 여러 객체들 간에 책임을 분산시키는 것입니다. 이를 통해 클라이언트는 요청의 처리 방식이나 순서를 알 필요 없이 요청만 전달하면 됩니다.

 

책임 연쇄 패턴의 구조

책임 연쇄 패턴의 기본 구조는 다음과 같습니다.

  1. Handler (처리자):
    • 요청을 처리하거나, 처리하지 못하면 다음 처리자에게 요청을 전달하는 인터페이스를 정의합니다.
  2. ConcreteHandler (구체적인 처리자):
    • Handler 인터페이스를 구현하여 요청을 처리하거나 다음 처리자로 전달합니다.
  3. Client (클라이언트):
    • 체인의 첫 번째 처리자에게 요청을 전달합니다. 요청은 처리될 때까지 체인을 따라 전달됩니다.

책임 연쇄 (Chain of Responsibility)

책임 연쇄 패턴의 구현 방법

예제 코드: 고객 요청 처리 시스템

고객 지원 시스템에서 고객의 요청을 처리하는 예제를 구현해보겠습니다. 요청은 일반 요청, 매니저 요청, CEO 요청으로 나뉘며, 각 요청은 해당하는 책임자에 의해 처리됩니다.

// Handler 인터페이스
public abstract class Handler
{
    protected Handler _nextHandler;

    public void SetNext(Handler nextHandler)
    {
        _nextHandler = nextHandler;
    }

    public abstract void HandleRequest(string request);
}

// ConcreteHandler1: 일반 요청 처리자
public class CustomerSupport : Handler
{
    public override void HandleRequest(string request)
    {
        if (request == "General")
        {
            Console.WriteLine("CustomerSupport: Handling general request.");
        }
        else if (_nextHandler != null)
        {
            Console.WriteLine("CustomerSupport: Passing request to the next handler.");
            _nextHandler.HandleRequest(request);
        }
    }
}

// ConcreteHandler2: 매니저 요청 처리자
public class Manager : Handler
{
    public override void HandleRequest(string request)
    {
        if (request == "Manager")
        {
            Console.WriteLine("Manager: Handling manager-level request.");
        }
        else if (_nextHandler != null)
        {
            Console.WriteLine("Manager: Passing request to the next handler.");
            _nextHandler.HandleRequest(request);
        }
    }
}

// ConcreteHandler3: CEO 요청 처리자
public class CEO : Handler
{
    public override void HandleRequest(string request)
    {
        if (request == "CEO")
        {
            Console.WriteLine("CEO: Handling CEO-level request.");
        }
        else if (_nextHandler != null)
        {
            Console.WriteLine("CEO: Passing request to the next handler.");
            _nextHandler.HandleRequest(request);
        }
        else
        {
            Console.WriteLine("CEO: No handler available for this request.");
        }
    }
}

// Client 코드
class Program
{
    static void Main()
    {
        // 처리자 체인 구성
        Handler customerSupport = new CustomerSupport();
        Handler manager = new Manager();
        Handler ceo = new CEO();

        customerSupport.SetNext(manager);
        manager.SetNext(ceo);

        // 요청 처리
        Console.WriteLine("Sending a general request:");
        customerSupport.HandleRequest("General");

        Console.WriteLine("\nSending a manager request:");
        customerSupport.HandleRequest("Manager");

        Console.WriteLine("\nSending a CEO request:");
        customerSupport.HandleRequest("CEO");

        Console.WriteLine("\nSending an unhandled request:");
        customerSupport.HandleRequest("Unknown");
    }
}

실행 결과

Sending a general request:
CustomerSupport: Handling general request.

Sending a manager request:
CustomerSupport: Passing request to the next handler.
Manager: Handling manager-level request.

Sending a CEO request:
CustomerSupport: Passing request to the next handler.
Manager: Passing request to the next handler.
CEO: Handling CEO-level request.

Sending an unhandled request:
CustomerSupport: Passing request to the next handler.
Manager: Passing request to the next handler.
CEO: No handler available for this request.

 

책임 연쇄 패턴의 장단점

장점

  1. 요청 처리의 유연성 증가:
    • 처리자들을 동적으로 추가하거나 제거하여 체인을 쉽게 변경할 수 있습니다.
  2. 책임 분산:
    • 요청을 처리하는 책임이 여러 객체에 분산되어 단일 처리자에 과도한 책임이 부여되지 않습니다.
  3. 단일 책임 원칙(SRP) 준수:
    • 각 처리자는 하나의 책임만 가지며, 요청 처리와 전달 역할을 분리할 수 있습니다.

단점

  1. 체인 설정의 복잡성:
    • 체인의 순서와 설정이 복잡해질 수 있으며, 잘못된 설정으로 인해 요청이 처리되지 않을 위험이 있습니다.
  2. 성능 문제:
    • 체인 길이가 길어질수록 요청 전달 과정에서 성능이 저하될 수 있습니다.
  3. 디버깅 어려움:
    • 요청이 어느 처리자에서 처리되었는지 추적하기 어려울 수 있습니다.

 

언제 책임 연쇄 패턴을 사용해야 할까?

책임 연쇄 패턴은 다음과 같은 상황에서 유용합니다.

  1. 요청 처리자와 요청 발신자를 분리해야 할 때:
    • 클라이언트가 어떤 객체가 요청을 처리할지 알 필요가 없을 때.
  2. 여러 처리자가 요청을 처리할 가능성이 있을 때:
    • 요청이 처리될 때까지 여러 객체가 처리할 기회를 가져야 할 때.
  3. 처리 순서를 동적으로 변경할 필요가 있을 때:
    • 요청 처리의 순서를 실행 중에 변경하거나 조정해야 할 때.

 

책임 연쇄 패턴과 관련 패턴 비교

책임 연쇄 패턴은 요청을 처리할 책임을 연쇄적으로 전달한다는 점에서 커맨드 패턴데코레이터 패턴과 유사하지만 목적이 다릅니다.

  • 커맨드 패턴:
    • 요청을 캡슐화하여 요청 자체를 재사용하거나, 실행/취소를 지원하는 패턴입니다.
    • 요청의 처리보다는 요청의 캡슐화와 실행 로직에 초점이 맞춰져 있습니다.
  • 데코레이터 패턴:
    • 객체에 기능을 동적으로 추가하기 위한 패턴으로, 요청 처리보다는 객체의 확장에 초점이 맞춰져 있습니다.
  • 책임 연쇄 패턴:
    • 요청을 처리할 객체를 동적으로 선택하거나, 요청이 처리될 때까지 객체를 순차적으로 전달하는 데 초점이 맞춰져 있습니다.

 

마무리하며

책임 연쇄 패턴은 요청 처리와 요청 전달을 분리하여 유연하고 확장 가능한 설계를 지원하는 강력한 패턴입니다. 특히 요청이 여러 객체에 의해 처리될 수 있는 경우, 객체 간의 결합도를 낮추고 처리 과정을 동적으로 변경할 수 있는 유연성을 제공합니다.

클라이언트가 요청의 처리 과정에 대해 알 필요가 없고, 요청을 동적으로 전달하거나 처리자 체인을 변경해야 하는 경우 책임 연쇄 패턴을 활용해보세요. 이를 통해 코드의 유지보수성과 확장성을 크게 향상시킬 수 있습니다.

반응형