책정리/GoF 디자인 패턴

17장 수행 가능 객체까지 요청 전파 문제(Chain of Responsibility 패턴)

GONII 2019. 2. 6. 03:55
    어떤 객체에 서비스 요청이 전달되었는데, 객체에서는 해당 서비스 요청을 처리할 없다고 가정해보자. 경우 객체가 취할 있는 행동은 어떤 것들이 있을까?
    크게 3가지 행동이 가능할 것이다.
    하나는 오류나 예외 상황 발생으로 처리하는 방법
    두번째는 전달된 요청과 가장 유사한 요청 처리 방식에 따라 작업을 수행해주는 방법
    마지막으로 해당 서비스 요청을 가장 처리할 있는 객체에게 요청을 전파해서 대신 처리하도록 만드는 방법
    Client 입장에서 가장 편리하고 바람직한 것은 서비스 요청을 가장 처리할 있는 객체까지 요청이 전파되어 처리가 이루어지는 세번째 방법일 것이다. 왜냐하면 경우에는 Client 일일이 요청 처리 결과를 판별해서 다른 객체에게 재요청을 필요가 없고, 객체들간 요청 전파를 통해 어느 객체의 판단에 의한 부분 최적 해가 아니라 전체 최적 해에 따른 처리가 수행될 것이기 때문이다.
    장에서는 어떤 객체에게 서비스 요청이 전달되었을 요청을 가장 처리할 있는 객체까지 요청을 전파해서 처리하게 만들어주는 디자인패턴을 살펴본다.
    • 문제 사례 설명
    이용자가 처한 상황을 시스템이 판별해서 이용자에게 알맞은 도움말을 제공하는 형태를 Context Sensitive 도움말 시스템이라고 한다. 그런데 같은 도움말 시스템이 제대로 수행되기 위해서는 이용자의 상황을 판별하는 기준과 그에 따라 보여주어야 도움말들이 연계, 정의되어 있어야 한다.
    그런데 실제로 모든 사용자 인터페이스 객체마다 적절한 도움말을 정의한다는 것은 불가능하다. 왜냐하면 사용자 인터페이스 객체 중에는 이용자가 어떤 상황에 처했는지와는 무관하게 일반적이거나 공통적으로 사용되는 객체들이 있을 있기 때문이다. 예를 들어 '확인' 버튼과 같은 사용자 인터페이스 객체를 생각해보면 버튼은 프린트 다이얼로그 박스 상에서도 보여질 있고, 파일 저장을 위한 다이얼로그 박스에서도 보여질 있으며, 다른 곳에서도 활성화되어 보여질 있다. 따라서 '확인' 버튼과 같은 경우에는 그것과 연계된 도움말을 정확히 결정하는 것이 불가능할 것이다.
    그렇다면 현재 활성화된 사용자 인터페이스 객체를 기준으로 Context Sensitive 도움말 시스템을 구축하고자 , 현재 활성화된 사용자 인터페이스 객체와 연계해서 정의된 도움말이 없을 경우에는 어떤 식으로 도움말을 보여주는 것이 좋을까?
    • 다양한 접근 방법 및 CHAIN OF RESPONSIBILITY 패턴
    주어진 문제를 정리하면 다음과 같다.
    개발하고자 하는 시스템은 그래픽 사용자 인터페이스(GUI) 환경에서 수행되는 도움말 제공 시스템이다. 그런데 시스템은 이용자의 편의를 도모하기 위해 이용자가 도움말을 요청할 경우 현재 활성화된 사용자 인터페이스 객체를 기준으로 이용자가 원하는 도움말을 미리 판별해서 보여주려고 한다. 이를 위해 시스템은 사용자 인터페이스 객체마다 도움말이 요청될 보여줄 도움말을 미리 지정해두는 형태로 접근하였다.
    여기서 문제는 사용자 인터페이스 객체 중에는 이용자가 처한 상황과는 무관하게 일반적으로 사용되는 객체들도 많이 있기 때문에 모든 사용자 인터페이스 객체마다 도움말을 지정할 수는 없다는 것이다. 그렇지만 이렇게 도움말이 지정되지 않은 사용자 인터페이스 객체에 대해서도 도움말이 요청될 있다. 같은 경우 Context Sensitive 도움말 제공 시스템은 어떤 식으로 이용자가 원하는 도움말을 제공해줄 있겠는가?
    • 기본적인 방법: 전담 객체 정의 방식
    도움말 제공 전담 객체를 정의하고 객체가 모든 도움말 처리를 수행하게 만드는 것이다.
    경우 도움말 제공 전담 객체는 현재 활성화된 사용자 인터페이스 객체를 전달받아 객체에 지정된 도움말이 있는지를 확인할 있어야 하고, 만약 지정된 도움말이 없으면 별도의 방법으로 현재 이용자가 처한 상황에 알맞은 도움말을 판별해서 보여줄 있어야 것이다. 같은 점을 고려할 경우 도움말 제공 전담 객체를 위한 클래스는 [그림 17-1] 같은 형태로 정의될 있을 것이다.


    [그림 17-1]에서 HelpHandler 클래스는 도움말을 실제로 처리하기 위한 HandleHelp() 멤버 함수와 사용자의 상황을 설정받기 위한 SetUserState() 멤버 함수를 인터페이스로 가진다. 또한 SetUserState() 멤버 함수에 의해 설정된 사용자의 상태를 저장, 관리하기 위한 userState_ 데이터 멤버도 가지고 있다.
    이런 구성으로 도움말 제공 전담 객체 방식은 문제에서 해결하고자 했던 도움말이 지정되지 않은 사용자 인터페이스 객체에 대해서도 이용자가 처한 상황에 가장 적절한 도움말을 제공하는 것이 가능함을 있다.
    [그림 17-1] 같은 방식이 실제로 적용될 경우 가장 문제가 부분은 바로 이용자 상태를 저장, 관리하고 그것에 따라 서로 다른 동작을 수행하는 분기 문장이 존재한다는 점이다. 왜냐하면 HelpHandler 클래스 객체에게 그것을 설정해야 하는 것이 일차적으로 불편한 점이며, 이용자가 머물 있는 상태가 새로 추가되거나 변경될 경우 거기에 따라 HandleHelp() 멤버 함수 내부의 분기 문장을 수정해야 하는 문제도 발생하기 때문이다.
    • 패턴 활용 방식: CHAIN OF RESPONSIBILITY 패턴
    해결해야할 문제는 도움말이 지정되지 않은 사용자 인터페이스 객체에 대해 어떻게 이용자가 처한 상황을 판별해서 도움말을 보여줄 것인가이다. 그런데 사용자 인터페이스 객체들을 살펴보면 일반적이거나 공통적으로 사용되어 도움말을 지정할 없는 객체들은 대부분 도움말 지정 가능한 다른 사용자 인터페이스 객체의 구성원이 된다는 것이다.
    예를 들어 '확인', '취소' 같은 버튼이나 여러 중에서 선택하기 위한 리스트 박스 또는 텍스트를 직접 입력할 있는 사용자 인터페이스 객체들은 일반적이거나 공통적으로 사용될 있는 객체들이기 때문에 도움말이 지정될 없지만, 이들 객체들은 대부분 낱개로 사용되는 것이 아니라, 다른 사용자 인터페이스 객체를 구성하기 위한 목적으로 사용된다.
    도움말이 지정되지 않은 사용자 인터페이스 객체가 활성화되어 있는 상태에서 이용자가 도움말을 요청하면, 요청을 받은 사용자 인터페이스 객체는 자신을 포함하고 있는 다른 사용자 인터페이스 객체에게 대신 요청을 처리하도록 자신이 받은 요청을 전달하는 것이다. 그리고 요청을 전달받은 객체는 또다시 자신에게 지정된 도움말이 없으면 자신을 감싸고 있는 다른 사용자 인터페이스 객체에게 요청을 전달해서 최종적으로 도움말이 지정된 객체가 자신에게 지정된 도움말을 보여주도록 하는 것이다.
    같은 접근에서도 문제가 있는 것은 사용자 인터페이스 객체들의 포함 관계에 따른 체인을 끝까지 따라갔음에도 도움말이 지정된 객체가 없을 경우 어떻게 것인가 하는 점이다. 이런 경우 가지 방법은 응용 프로그램 객체가 최종적으로 도움말 요청을 전달받아 가장 기본적인 도움말 처리라도 해주는 것이다.
    그렇다면 클래스 구조를 설계한다면 어떤 형태가 좋을까?
    우선 고려해야 것은 객체들간에 도움말 요청이 전달될 사용하는 인터페이스가 동일해야 한다는 점이다. 왜냐하면 요청을 전달하는 인터페이스가 객체의 자료형마다 다르게 되면, 요청을 전달하기에 앞서 요청을 전달받을 객체의 자료형을 구분해야 하는데 이는 불편할 뿐만 아니라 새로운 클래스가 추가로 정의될 경우 기존 소스코드를 수정해야 하는 문제를 유발시킬 있기 때문이다.
    따라서 객체들간에 도움말 요청을 전달하기 위한 인터페이스는 동일해야 하는데, 이를 위한 가장 적절한 방식은 도움말 요청이 상호 전달될 있는 객체들의 클래스를 모두 동일한 클래스 상속 구조하에 두는 것이다.
    한편 클래스 구조 설계에서 고려해야 다른 하나는 객체들이 도움말 요청을 처리하지 못할 경우 이를 처리하도록 도움말 요청을 전달할 객체가 지정될 있어야 한다는 것이다. 물론 요청이 전파될 객체들간의 관계는 동적으로 구성되는 것이지만, 어떤 객체에게 요청을 전파할지는 데이터 멤버로 저장, 관리되어야 한다는 의미이다. 이때 도움말 요청이 전달될 객체들의 클래스가 모두 동일한 상속 구조 하에 있으면, 이같은 데이터 멤버는 최상위 클래스를 활용해서 동일한 자료형으로 표현될 있을 것이다.


    [그림 17-2] 클래스 구조에 의해 실제 버튼에서부터 프린트 다이얼로그 박스, 응용 프로그램 객체로까지 도움말 요청이 전달되는 형태를 살펴보면 [그림 17-3] 같을 것이다. 물론 aButton 객체의 pSuccessor_ aPrintDialog 객체로, aPrintDialog 객체의 pSuccessor_ anApplication 객체로 설정되어 있어야 할것이다.


    이처럼 객체들간의 관계에 의해 체인을 구성해두고 특정 객체에게 요청이 전달될 경우, 해당 객체가 요청을 처리하지 못하면 체인 상에 있는 다른 객체에게 요청을 대신 처리하게 하는 방식의 설계를 Chain of Responsibility 패턴이라고 한다. 이때 객체들간의 체인 구성은 정적으로 뿐만 아니라 동적으로도 가능하며, Client 요청이 어떤 객체에 의해 처리되는지를 신경쓰지 않아도 된다는 장점이 있다.
    • 샘플 코드
    [소스 17-1] Context Sensitive 도움말 시스템을 위한 Chain of Responsibillity 패턴 적용 코드
    #include <iostream>
    #include <string>
    using namespace std;
     
    class HelpHandler
    {
    public:
    HelpHandler(HelpHandler* pObj = 0, string helpMsg = "")
    {
    pSuccessor_ = pObj;
    helpMsg_ = helpMsg;
    }
     
    virtual void SetHandler(HelpHandler* pObj, string helpMsg)
    {
    pSuccessor_ = pObj;
    helpMsg_ = helpMsg;
    }
     
    virtual bool HasHelp() { return !helpMsg_.empty(); }
    virtual void HandleHelp()
    {
    if (pSuccessor_)
    {
    pSuccessor_->HandleHelp();
    }
    }
     
    virtual void ShowHelpMsg()
    {
    cout << helpMsg_ << endl;
    }
    protected:
    HelpHandler* pSuccessor_;
    string helpMsg_;
    };
     
    class Widget : public HelpHandler
    {
    protected:
    Widget(Widget* pObj, string helpMsg = "")
    : HelpHandler(pObj, helpMsg)
    {
    pParent_ = pObj;
    }
    private:
    Widget* pParent_;
    };
     
    class Button : public Widget
    {
    public:
    Button(Widget* pObj, string helpMsg="")
    : Widget(pObj, helpMsg)
    {}
     
    virtual void HandleHelp()
    {
    if (HasHelp())
    ShowHelpMsg();
    else
    HelpHandler::HandleHelp();
    }
     
    virtual void ShowHelpMsg()
    {
    cout << helpMsg_ << endl;
    }
    };
     
    class Dialog : public Widget
    {
    public:
    Dialog(HelpHandler* pObj, string helpMsg = "")
    :Widget(0)
    {
    SetHandler(pObj, helpMsg);
    }
     
    virtual void HandleHelp()
    {
    if (HasHelp())
    ShowHelpMsg();
    else
    HelpHandler::HandleHelp();
    }
     
    virtual void ShowHelpMsg()
    {
    cout << helpMsg_ << endl;
    }
    };
     
    class Application : public HelpHandler
    {
    public:
    Application(string helpMsg)
    : HelpHandler(0, helpMsg)
    {}
     
    virtual void HandleHelp()
    {
    ShowHelpMsg();
    }
     
    virtual void ShowHelpMsg()
    {
    cout << helpMsg_ << endl;
    }
    };
     
    void main()
    {
    Application* pApp = new Application("Application Help");
    Dialog* pDialog = new Dialog(pApp, "Dialog Help");
    Button* pButton = new Button(pDialog);
     
    pButton->HandleHelp();
    }
    [소스 17-1]에서 보면 Button 객체에 대해 HandleHelp() 멤버 함수가 호출되었지만 실제 결과는 Dialog 객체에 의해 설정된 도움말 메시지가 출력되는 것을 확인하 ㄹ수 있다. 이는 Button 객체의 HandleHelp() 멤버 함수에서 도움말 메시지가 지정되어 있지 않으면, HelpHandler 클래스의 HandleHelp() 멤버 함수를 통해 pSuccessor_ 지정된 객체의 HandleHelp() 멤버 함수를 수행하기 때문이다.
    • 구현 관련 사항
    Chain of Responsibility 패턴의 구현에서 가장 먼저 고민해야 부분은 바로 체인을 어떻게 구성할 것인가 하는 문제다. 여기에는 크게 2가지 방식이 있는데, 하나는 체인을 구성하기 위한 링크를 새로 정의하는 것이고, 다른 하나는 이미 존재하는 링크를 이용하는 것이다.
    대부분의 경우에는 전자 방식에 의해 체인을 구성하 ㄹ수 밖에 없으나, Composite 패턴이 적용된 경우에는 후자 방식이 유용할 경우가 많다. 특히 구성하려는 체인의 구조가 Composite 패턴에 의해 생성되는 부분-전체 관계와 일치할 경우에는 별도의 링크 생성 비용없이 사용할 있어 편리하다. 반면 Composite 패턴이 적용되었다 하더라도 구성하려는 체인의 구조와 Composite 패턴에 의해 생성되는 객체간의 연결 구조가 다를 경우에는 어쩔 없이 전자 방식을 따를 밖에 없다.
    이미 존재하는 링크를 사용할 없을 경우에는 [소스 17-1] HelpHandler클래스와 같이 pSuccessor_ 객체의 관리를 수행해야 것이다. 이처럼 최상위 클래스가 pSuccessor_ 객체의 관리를 수행할 경우에는 최상위 클래스에서 요청에 대한 기본 처리를 구현할 있는데, 그것은 pSuccessor_ 객체에게 요청을 전달하는 형태가 것이다. 물론 같은 요청 처리는 하위 클래스에 의해 재정의될 것이며, 이를 통해 하위 클래스 객체는 자신이 처리 가능한 요청을 수행하게 것이다.
    체인을 통해 전달될 요청의 종류가 여러 가지일 경우 어떻게 처리할 것인가도 고려해야 한다. 가장 간단한 방법으로는 요청의 종류마다 새로운 인터페이스를 정의해서 사용하는 것이다. 그러나 방법은 처리해야 요청의 종류가 많아질 경우 너무 많은 인터페이스를 추가해야 하기 때문에 불편하고, 유지 보수도 힘들다.
    이런 문제를 해결하기 위한 방법으로 인터페이스는 하나로 정의하되, 요청의 종류를 인자로 전달받는 것이다. 이때 요청의 종류를 전달받은 방식으로는 요청의 종류마다 미리 코드 값을 정해두고 이를 전달받는 방식과 다양한 요청들을 클래스로 정의하고 상위에 Request 같은 클래스를 정의해서 상속 구조를 만들어 사용하는 방식이 있을 있다. 이때 전자 방식은 코드 값을 미리 약속해야 한다는 불편함 뿐만 아니라 인자로 전달된 요청에 대해 자료형 검증 등이 불가능하다는 단점이 있으므로, 가능하다면 후자 방법을 택하는 것이 바람직할 것이다.
    후자 방법의 경우에도 구체적인 구현에 있어서 [소스 17-2] [소스 17-3]에서 보는 것처럼 2가지 형태가 가능하다. [소스 17-2] C++ 같이 RTTI 지원하는 프로그래밍 언어의 경우 사용할 있으며, 특정 자료형으로 객체를 변환시켜봄으로써 자료형 검사를 수행하는 방식이다.
    [소스 17-3] 경우에는 프로그래밍 언어의 특성에 의존하지 않고 Request 클래스에 요청의 종류를 판별해주는 멤버 함수를 정의해둠으로써 인자로 전달된 요청의 자료형을 판별할 있게 만들어주는 방식이다. [소스 17-3] 경우에는 프로그래밍 언어의 특성에 의존하지 않고 Request  클래스에 요청의 종류를 판별해주는 멤버 함수를 정의해둠으로써 인자로 전달된 요청의 자료형을 판별할 있게 만들어주는 방식이다.
    [소스 17-2] dynamic_cast 통해 인자로 전달된 요청의 자료형을 구분하는
    #include <iostream>
    using namespace std;
     
    class Request
    {
    public:
    virtual ~Request() {}
    };
     
    class HelpRequest : public Request {};
    class PrintRequest : public Request {};
     
    class Handler
    {
    public:
    Handler(Handler* pObj)
    : pSuccessor_(pObj)
    {}
    virtual void HandleRequest(Request* pReq)
    {
    if (dynamic_cast<HelpRequest*>(pReq) != NULL)
    {
    cout << "HelpRequest Processing" << endl;
    }
    else if (dynamic_cast<PrintRequest*>(pReq) != NULL)
    {
    cout << "PrintRequest Processing" << endl;
    }
    else
    {
    cout << "Default Processing" << endl;
    }
    }
    private:
    Handler* pSuccessor_;
    };
     
    void main()
    {
    Handler hdlr(0);
    Request* pReq = new PrintRequest;
    hdlr.HandleRequest(pReq);
    }
    [소스 17-3] 별도로 정의된 멤버 함수를 활용해서 인자로 전달된 요청의 자료형을 구분하는
    #include <iostream>>
    using namespace std;
     
    #define DEFAULT_REQUEST                0
    #define HELP_REQUEST                1
    #define PRINT_REQUEST                2
     
    class Request
    {
    public:
    virtual int GetKind() { return DEFAULT_REQUEST;        }
    };
     
    class HelpRequest : public Request
    {
    virtual int GetKind() { return HELP_REQUEST; }
    };
     
    class PrintReqeust : public Request
    {
    public:
    virtual int GetKind() { return PRINT_REQUEST; }
    };
     
    class Handler
    {
    public:
    Handler(Handler* pObj)
    : pSuccessor_(pObj)
    {}
    virtual void HandleRequest(Request* pReq)
    {
    switch (pReq->GetKind())
    {
    case HELP_REQUEST:
    cout << "HelpRequest Processing" << endl;
    break;
    case PRINT_REQUEST:
    cout << "PrintReqeust Processing" << endl;
    break;
    default:
    cout << "Default Processing" << endl;
    break;
    }
    }
    private:
    Handler* pSuccessor_;
    };
     
    void main()
    {
    Handler hdlr(0);
    Request* pReq = new PrintReqeust;
    hdlr.HandleRequest(pReq);
    }
    여러 종류의 요청을 하나의 인터페이스에서 처리하게 되면 필연적으로 분기 문장이 생기게 되는데, 이것은 새로운 종류의 요청이 추가 정의될 경우 소스코드를 수정해야 하는 문제를 야기시킬 있다. 이런 문제를 해결할 있는 가지 방법은 [소스 17-4] 같이 Handler클래스를 확장해서 하위 클래스를 정의하고 이를 통해 새로운 종류의 요청을 처리하는 것이다. 이런 방식을 사용하게 되면 새로운 종류의 요청이 추가되더라도 기존 소스코드를 변경하지 않고 요청 처리가 가능하게 만들 있을 것이다.
    [소스 17-4] 새로운 종류의 요청을 클래스 상속을 통해 기존 소스코드의 수정없이 처리하는
    #include <iostream>
    using namespace std;
     
    class Request
    {
    public:
    virtual ~Request() {}
    };
     
    class HelpRequest : public Request {};
    class PrintRequest : public Request {};
    class PreviewRequest : public Request {};
     
    class Handler
    {
    public:
    Handler(Handler* pObj)
    : pSuccessor_(pObj)
    {}
     
    virtual void HandleRequest(Request* pReq)
    {
    if (dynamic_cast<HelpRequest*>(pReq) != NULL)
    {
    cout << "HelpRequest Processing" << endl;
    }
    else if (dynamic_cast<PrintRequest*>(pReq) != NULL)
    {
    cout << "PrintRequest Processing" << endl;
    }
    else
    {
    cout << "Default Processing" << endl;
    }
    }
    private:
    Handler* pSuccssor_;
    };
     
    class ExtendeHandler : public Handler
    {
    public:
    ExtendeHandler(Handler* pObj)
    : Handler(pObj)
    {}
     
    virtual void HandleRequest(Request* pReq)
    {
    if (dynamic_cast<PreviewRequest*>(pReq) != NULL)
    {
    cout << "Preview Request" << endl;
    }
    else
    {
    Handler::HandleRequest(pReq);
    }
    }
    };
     
    void main()
    {
    ExtendeHandler hdlr(0);
    Request* pReq = new PreviewRequest;
     
    hdlr.HandleRequest(pReq);
    }
    • CHAIN OF RESPONSIBILITY 패턴 정리
    Chain of Responsibility 패턴의 일반적인 클래스 구조는 [그림 17-4] 같다.
    Chain of Responsibility 패턴이 적용될 경우 객체들간 체인 구성 형태는 [그림 17-5] 같다.
    Chain of Responsibility 패턴이 주로 활용될 있는 분야는 다음과 같다.
    • 하나 이상의 객체가 요청을 처리할 있고, 미리 어떤 객체가 요청을 처리할지는 알지 못하며, 실제 요청을 처리하는 객체는 자동으로 결정되기를 원할 경우.
    • 구체적으로 수신자를 지정하지 않고 여러 객체 하나에게 요청을 전달하고자
    • 요청을 처리할 있는 객체들이 동적으로 명시되어야
    Chain of Responsibility 패턴의 장단점은 다음과 같다
    • Chain of Responsibility 패턴은 시스템 내의 결합도(Coupling) 낮추어 준다. 왜냐하면 요청을 하는 쪽이나 받는 쪽이나 정확히 누가 누구에게 요청을 한다는 사실을 필요가 없고, 단지 요청이 적절히 처리될 것이라는 사실만 알면 되기 때문이다. 따라서 요청을 하는 입장에서는 요청을 받을 가능성이 있는 객체들을 일일이 관리할 필요가 없다. 또한 체인 내에 있는 객체들도 체인이 어떻게 구성되었는지에 대해서는 필요가 없으며, 단지 자신의 pSuccessor_ 아는 것으로 충분하다. 이러한 사실들로 인해 Chain of Responsibility 패턴은 객체들간의 상호 연관 관계를 단순화시켜주어 시스템의 결합도를 낮춰준다.
    • Chain of Responsibility 패턴은 객체들간의 책임을 보다 유연하게 분산시킬 있다. 왜냐하면 패턴을 사용할 경우 체인을 구성하는 객체들을 동적으로 추가하거나 변경하는 것이 쉬워 객체들간의 책임 분산이 동적으로 쉽게 이루어질 있기 때문이다. 일반적으로 정적으로 책임을 분산하고자 경우에는 하위 클래스들을 정의해서 사용하는 것이 대표적인데 반해 동적으로 책임을 분산시키기 위해서는 Chain of Responsibility 패턴과 같은 방법을 활용하는 것이 바람직하다.
    • Chain of Responsibility 패턴의 가장 단점은 요청이 제대로 처리될지 여부가 불확실하다는 저미다. 체인이 적절하게 구성되어 있지 않거나, 체인의 끝까지 갔는데도 요청이 처리되지 않을 가능성은 얼마든지 있다. 이것은 정확히 어떤 객체가 요청을 처리할지 지정해주지 않았기 때문에 발생하는 문제다.
    • 요청을 처리하는데 걸리는 시간을 정확히 예측하기 힘들다는 단점도 있다. 왜냐하면 실제로 요청을 처리해줄 객체가 체인의 어느 위치에 있을지는 동적으로 변동될 있기 때문이다. 따라서 실시간 시스템과 같이 시간 예측이 중요한 경우에는 Chain of Responsibility 패턴의 적용을 재검토해야 것이다.
반응형