책임 연쇄 패턴(Chain of responsibility)

  • 책임 연쇄 패턴 또는 체인 패턴은 객체 메세지의 송신과 수신을 분리해서 처리합니다.
  • 어떤 조건에 따라 코드가 분기되는 이벤트에 많이 활용합니다.
  • 절차지향 개발에서는 조건문(if 또는 switch)을 사용하지만, 객체지향 개발에서는 객체를 활용하여 이벤트 핸들러(Event Handler)를 구현합니다.
  • 핸들러는 크게 정적 핸들러(Static Handler), 동적 핸들러(Dynamic Handler)로 구분합니다.
    • 정적 핸들러: 코드에서 미리 결정된 동작을 수행합니다.
    • 동적 핸들러: 런타임 상황에서 결정되는 동작을 수행합니다.

책임 연쇄 패턴 구조

  • 핸들러는 이벤트의 처리 로직을 분리하여 행동을 독립적으로 관리합니다.
  • 우리가 생각할 수 있는 가장 기본적인 핸들러로 시작해서 책임 연쇄 패턴으로 변경되는 과정을 살펴보겠습니다.

책임 연쇄 패턴: 기본적인 핸들러

  • 조건에 따라 이벤트를 관리하는 핸들러는 다음과 같이 작성할 수 있습니다.
class Order
{
public:
    std::string execute()
    {
        return "[Order] execute";
    }
}

class Cancel
{
public:
    std::string execute()
    {
        return "[Cancel] execute";
    }
}

class Handler
{
public:
    std::string event(std::string message)
    {
        if (message == "order")
        {
            return std::make_shared<Order>()->execute();
        }
        else if (message == "cancel")
        {
            return std::make_shared<Cancel>()->execute();
        }
    }
}
  • 이 핸들러는 발생된 이벤트에 따라 조건을 직접 검사하고 있습니다.
  • 따라서 작성한 순서대로 검사가 이뤄집니다.
  • 책임 연쇄 패턴의 아이디어는 핸들러에서 순차적으로 이벤트를 검사하는 조건을 분리하는 것입니다.

책임 연쇄 패턴: 이벤트 조건 검사가 분리된 핸들러

  • 핸들러에서 이벤트를 검사하던 내용이 분리됩니다.
class Order
{
public:
    std::string execute(std::string event)
    {
        if (event == "order")
        {
            return "[Order] execute";
        }
    }
}

class Cancel
{
public:
    std::string execute(std::string event)
    {
        if (event == "cancel")
        {
            return "[Cancel] execute";
        }
    }
}

class Handler
{
public:
    std::string event(std::string event)
    {
        std::string message = std::make_shared<Order>()->execute();
        if (!message.empty())
        {
            return message;
        }

        message = std::make_shared<Cancel>()->execute();
        if (!message.empty())
        {
            return message;
        }
    }
}
  • 검사를 단순 분리하였지만 여전히 부족한 코드입니다.
  • 검사 결과를 확인하는 과정이 핸들러에 남아있기 때문입니다.

책임 연쇄 패턴: 동작 객체의 인터페이스 클래스

  • 일반적으로 이벤트 핸들러는 하나의 상태값하나의 동작 객체를 지정합니다.
  • 반대로 하나의 상태값에 복수의 동작 객체를 지정하면 로직이 복잡해집니다.
  • 책임 연쇄 패턴은 하나의 상태값에 복수의 동작 객체를 처리할 수 있도록 동작 객체를 체인처럼 묶어서 처리합니다.
  • 기본적인 동작 방식은 연결 리스트(Linked List)와 비슷합니다.
  • 동작 객체를 하나로 묶기 위해 인터페이스 클래스를 작성합니다.
class Chain
{
public:
    void setNext(std::shared_ptr<Chain> next)
    {
        m_next = next;
    }

    virtual std::string execute(std::string event) = 0;

protected:
    std::shared_ptr<Chain> m_next;
};

책임 연쇄 패턴: 동작 객체의 구현

  • 이벤트의 조건을 검사하고 처리할 동작 객체를 구현합니다.
class Order :
    public Chain
{
public:
    std::string execute(std::string event) override
    {
        if (event == "order")
            return "[Order] execute";

        // 책임 연쇄 패턴의 핵심
        return m_next->execute(event);
    }
};

class Cancel :
    public Chain
{
public:
    std::string execute(std::string event) override
    {
        if (event == "cancel")
            return "[Cancel] execute";

        // 책임 연쇄 패턴의 핵심
        return m_next->execute(event);
    }
};

책임 연쇄 패턴: 핸들러 구현

  • 동작 객체를 체인으로 묶어서 처리하는 핸들러를 구현합니다.
class Handler
{
public:
    std::string event(std::string event)
    {
        // 어딘가 부족한 코드...
        auto first = std::make_shared<Order>();
        first->setNext(std::make_shared<Cancel>());

        return first->execute(event);
    }
};

책임 연쇄 패턴: 실행 코드

  • 책임 연쇄 패턴의 클라이언트는 매우 간단합니다.
int main(const int argc, const char* argv[])
{
    auto handler = std::make_shared<Handler>();
    std::cout << handler->event("cancel") << std::endl;

    return 0;
}
  • 실행 결과
[Cancel] execute

책임 연쇄 패턴: 부족한 핸들러 보완

  • 지금까지 작성한 핸들러는 부족한 부분이 존재합니다.
  • 동작 객체가 늘어남에 따라 복수의 핸들러가 필요한 경우가 있습니다.
  • 복수의 핸들러를 관리하기 위해 인터페이스 클래스를 작성하고 템플릿을 적용합니다.
// 동작 객체의 인터페이스
class IEvent
{
public:
    virtual std::string name() = 0;
};

// 동작 객체 A
class AEvent :
    public IEvent
{
public:
    std::string name() override
    {
        return "A";
    }
};

// 동작 객체 B
class BEvent :
    public IEvent
{
public:
    std::string name() override
    {
        return "B";
    }
};

// 핸들러 인터페이스 클래스
class IHandler
{
public:
    IHandler(std::shared_ptr<IHandler> handler) :
        m_handler(handler)
    {

    }

    virtual void event(std::shared_ptr<IEvent> user)
    {
        if (m_handler != nullptr)
        {
            m_handler->event(user);
        }
    }

private:
    std::shared_ptr<IHandler> m_handler;
};

// 핸들러 템플릿
template<typename T>
class HandlerTemplate :
    public IHandler
{
public:
    HandlerTemplate(std::shared_ptr<IHandler> handler) :
        IHandler(handler)
    {

    }

    std::string event(std::shared_ptr<IEvent> user) override
    {
        if (dynamic_cast<std::shared_ptr<T>>(user) != nullptr)
        {
            std::cout << user->name << " Event" << std::endl;
        }
        else
        {
            IHandler::event(user);
        }
    }
};