책임 연쇄 패턴(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;
}
책임 연쇄 패턴: 부족한 핸들러 보완#
- 지금까지 작성한 핸들러는 부족한 부분이 존재합니다.
- 동작 객체가 늘어남에 따라 복수의 핸들러가 필요한 경우가 있습니다.
- 복수의 핸들러를 관리하기 위해 인터페이스 클래스를 작성하고 템플릿을 적용합니다.
// 동작 객체의 인터페이스
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);
}
}
};