본문 바로가기
까망 동네/디자인 패턴

[디자인패턴] 체인 패턴 (Chain of Responsibility, 책임 연쇄)

by 까망 하르방 2023. 10. 27.
반응형

🎈 체인 패턴 (Chain of Responsibility)

• 책임 전가(고리, 연쇄)라고 불리는 패턴

• 처리할 수 있는 다른 객체 연결

 → 요청을 처리하거나 못해도 다음 객체로 전달한다.

• 요청 자체와 각 처리 객체 사이의 결합을 피한다.

 → 객체 메시지 송신과 수신 분리

• 하나의 객체에서 모든 처리를 구현하지 않을 수 있다.

    → 객체의 의존성 주입을 통한 위임

• 디자인 패턴에서 행위 패턴에 속한다.

 

💻 디자인 패턴(Design Pattern)이란?

👨‍💻 디자인 패턴(Design Pattern)이란? • SW 개발 방법 중에서도 구조적인 문제 해결에 목적을 둔다. • 알고리즘과 같이 특정 문제를 해결하는 Logic 형태보다는 특정 상황에 적용할 수 있는 방

zoosso.tistory.com


 

[체인 패턴 예제]

#include <iostream>

struct Handler
{
    Handler* next = nullptr;

    void Handle(int issue)
    {
        // 처리 가능한 경우
        if (Process(issue))
        {
            std::cout << " ok" << std::endl;
            return;
        }
            
        // 처리하지 못하고, 다음 처리 객체가 남아 있는 경우
        if (next != nullptr)
            next->Handle(issue);
    }

    virtual bool Process(int problem) = 0;
};


class MinusHandler : public Handler
{
public:
    bool Process(int issue) override
    {
        std::cout << " Minus Handler -";
        bool b = issue < 0; // 음수
        return b;
    }
};

class OddHandler : public Handler
{
public:
    bool Process(int problem) override
    {
        std::cout << " Odd Handler -";
        bool ret = problem % 2 == 1; // 홀수
        return ret;
    }
};

class EvenHandler : public Handler
{
public:
    bool Process(int problem) override
    {
        std::cout << " Even Handler -";
        bool ret = problem % 2 == 0; // 짝수
        return ret;
    }
};


int main()
{
    MinusHandler minusHandle;
    EvenHandler  evenHandle;
    OddHandler   oddHandle;
    
    minusHandle.next = &evenHandle;
    evenHandle.next = &oddHandle;
    oddHandle.next = nullptr;

    minusHandle.Handle(-2);
    minusHandle.Handle(10);
    minusHandle.Handle(13);
}

 

책임 연쇄 패턴

Handler 역할로 Minus, Odd, Even 객체를 만들고 연결해주었다.

- 시작하는 Handler를 설정 가능

- 우선순위에 따라 처리되는 Handler 순서 설정 가능

- 새로운 Handler 객체를 만들어 연결 해주면 된다.


 

[예제 코드]

책임 연쇄 패턴 적용 X  조건문 활용

#include <iostream>
#include <string>

class Logger {
public:
    Logger(int level) : level(level) {}

    void log(const std::string& message) {
        if (level == 1 && Logger::logLevel >= 1) {
            std::cout << "Error Logger: " << message << std::endl;
        }
        if (level == 2 && Logger::logLevel >= 2) {
            std::cout << "File Logger: " << message << std::endl;
        }
        if (level == 3 && Logger::logLevel >= 3) {
            std::cout << "Console Logger: " << message << std::endl;
        }
    }

    static void setLogLevel(int level) {
        logLevel = level;
    }

private:
    int level;
    static int logLevel;
};

int Logger::logLevel = 3;

void main() {
    Logger::setLogLevel(3);

    Logger errorLogger(1);
    Logger fileLogger(2);
    Logger consoleLogger(3);

    errorLogger.log("This is an error message.");
    fileLogger.log("This is a file log message.");
    consoleLogger.log("This is a console log message.");
}

 

책임 연쇄 패턴 예시

책임 연쇄를 통해 각 Logger에 Error Message를 출력할 수 있다.

각 객체가 단일 책임을 가지게 되었는데

새로운 Logger 유형 추가된다면 수정하기 쉬워졌다.


 

🎈 체인 패턴 장단점

+ 연쇄 구성에 따라 동적으로 처리자 변경/확장할 때 유용

+ 객체간 느슨한 결합 (객체 변경시 영향도 최소화)

+ 단일 책임 원칙 준수: 각 처리자는 하나의 책임을 갖도록 설계

 

- 순차적으로 객체를 처리하기 다소 지연 시간 발생

- 요청이 처리 과정을 따라가지 않을 수 있으며 예측하기 어려울 수 있음

- 적절한 종료 조건이 없으면 무한 Loop에 빠질 수 있음

 

 

🎈 활용 예시

Event 처리 시스템: GUI 라이브러리나 WEB 이벤트 처리와 같이 다양한 이벤트 Handler 처리

Log 처리: 각 로그 메시지의 유형에 따른 처리

보안 시스템: 보안 규칙에 따른 권한 부여

미들웨어: 서버에 도달하기 전에 다양한 처리 가능


[예제 코드]

ATM에서 지출 요청을 처리

#include <iostream>
#include <string>

class Request {
public:
    Request(int amount) : amount(amount) {}
    int getAmount() const { return amount; }
    void setAmount(int _amount) { amount = _amount; }

private:
    int amount;
};

class Handler {
public:
    Handler(Handler* successor = nullptr) : successor(successor) {}


    virtual void handleRequest(Request& request) {
        if (successor) {
            successor->handleRequest(request);
        }
    }


    void setSuccessor(Handler* successor) {
        this->successor = successor;
    }

protected:
    Handler* successor;
};


class Company_B : public Handler {
public:
    Company_B(Handler* successor = nullptr) : Handler(successor) {}


    void handleRequest(Request& request) override {
        std::cout << "[B]: OK! " << request.getAmount() << std::endl;
    }
};

class Company_A : public Handler {
public:
    Company_A(Handler* successor = nullptr) : Handler(successor) {}


    void handleRequest(Request& request) override {
        if (request.getAmount() <= 100) {
            std::cout << "[A]: OK! " << request.getAmount() <<  std::endl;
        }
        else {
            std::cout << "[A]: Sorry... -> ";
            successor->handleRequest(request);
        }
    }
};

void main() {
    Company_B objB;
    Company_A objA;

    objA.setSuccessor(&objB);

    Request request(90);
    objA.handleRequest(request);

    request.setAmount(150);
    objA.handleRequest(request);
}

 

책임 연쇄 패턴 예시

amount 100 이하는 Company_A에서 처리할 수 있지만

그것보다 많은 수량은 Company_B에서 처리 가능하다.


 

🎈 다른 디자인 패턴과 비교

• 장식자 (Decorator)

객체를 감싸서 객체에 새로운 기능을

동적으로 추가하는데 사용되는 패턴

 

[디자인패턴] 장식자 패턴 (Decorator)

🎈 데코레이터 패턴 (Decorator Pattern) • 객체에 동적으로 기능을 추가하거나 변경할 수 있게 해주는 패턴 • 압축, 암호화, 버퍼링 기능 추가시 활용해볼 수 있다. • 디자인 패턴 중 구조 패턴에

zoosso.tistory.com

 

• 전략 (Strategy)

특정 행동(알고리즘)을 캡슐화하고, 런타임에 알고리즘 교체

객체의 동작을 변경하려는 경우 사용

 

💻 [디자인패턴] 전략 패턴 (Strategy Pattern)

전략 패턴 (Strategy Pattern) 이란? 전략(Strategy)은 코드 내부에서 로직(Logic)을 처리하는 「알고리즘」 어떤 목적 달성을 위한 수행 방식이라고 생각하면 좋다. 영화관에서 이벤트 영화 예매 방식을

zoosso.tistory.com

 

• 커맨드 (Command)

명령을 캡슐화하고 수신자에 전달하여 실행

요청과 실행을 분리하려는 경우에 사용된다.

 

[디자인패턴] 커맨드 패턴 (Command Pattern)

🎈 커맨드 패턴 (Command Pattern) • 명령을 캡슐화하고 실행하기 위해 사용 • GUI 응용 프로그램, 트랜잭션 처리, 큐 관리 등에 활용 가능 • 명령 이력 관리, 역명령 실행(Undo), 동적 선택과 같은 상

zoosso.tistory.com

 

• 관찰자 (Observer)

객체 상태가 변경될 때 관찰자에게 알려주면

그 변화를 다수의 객체에게 알리고자할 때 사용

 

[디자인 패턴] 관찰자(Observer) 패턴

🎈 감시자 패턴 (Observer Pattern) • 어떤 객체 상태가 변경되면 다른 객체(observer)에게 알리는 디자인 패턴 → 의존관계에 있는 모든 객체들이 통지받고 자동으로 갱신 → 1:多 형태로 Broadcast로 활

zoosso.tistory.com

반응형

댓글