개발/Architecture & Design Pattern

C++ - 역할 사슬, CoR 패턴 (Chain of Responsibility Pattern)

피로물든딸기 2024. 2. 29. 18:27
반응형

C, C++ 전체 링크

Architecture & Design Pattern 전체 링크

 

참고

- 클래스 다이어그램 그리기

 

역할 사슬, CoR 패턴 (Chain of Responsibility Pattern) - 행동 패턴

- 요청을 보내는 객체와 요청을 처리할 수 있는 여러 객체 사이에 역할 사슬을 구성하는 패턴

- 요청을 처리하는 여러 개의 객체가 있는 경우, 요청을 처리할 객체를 결정할 수 있다.

 

구현

- Handler : 요청을 처리할 인터페이스와 다음 요청을 처리할 successor 참조를 제공한다.

- ConcreteHandler : 실제로 요청을 처리하고, 처리하지 못하는 경우 successor에게 요청을 전달한다.

- 클라이언트는 요청을 최초의 핸들러에게 전달한다.

 

장점

- SRP(단일 책임 원칙), 요청을 처리하는 객체와 처리하는 객체를 분리한다.

OCP(개방-폐쇄 원칙), 기존 코드를 수정하지 않고 새로운 핸들러를 추가할 수 있다.

- 요청 처리 순서를 런타임에 변경할 수 있다.

- 클라이언트는 요청을 누가 처리하는지 몰라도 된다.

 

단점

- 요청이 수행되지 않는 경우에 대한 처리가 필요하다.

- 요청을 전달하는 과정에서 성능 저하가 발생할 수 있다.

- 클라이언트가 명시적으로 요청을 처리할 Handler를 지정할 수 없다.


메일 분류함

 

메일의 내용을 확인해서 스팸 / 중요 메일함 / 일반 메일함으로 분류한다고 가정해 보자.

 

예시 코드는 아래와 같다.

#include <iostream>
#include <string>

using namespace std;

class Client 
{
public:
	void classifyEmail(const string& mail) 
	{
		if (mail.find("spam") != string::npos)
			cout << "This e-mail is classified as spam." << endl;
		else if (mail.find("important") != string::npos)
			cout << "This e-mail is classified as important." << endl;
		else
			cout << "This e-mail is classified as normal." << endl;
	}
};

int main() 
{	
	Client* client = new Client();
	
	client->classifyEmail("This is a normal mail.");
	client->classifyEmail("This is an important mail.");
	client->classifyEmail("This is a spam mail.");

	return 0;
}

 

새로운 메일함이 추가되면 else if 구문만 추가하면 된다.

if (mail.find("spam") != string::npos)
	cout << "This e-mail is classified as spam." << endl;
else if (mail.find("important") != string::npos)
	cout << "This e-mail is classified as important." << endl;
// else if New Mail Box
else
	cout << "This e-mail is classified as normal." << endl;

 

하지만 실제 메일을 분류할 때는 단순히 find로 키워드를 찾아서 메일을 분류하지 않는다.

더 까다로운 문자열 판단 조건누가 발송했는지 등 여러 조건으로 메일을 분류할 것이다.

그리고 메일을 분류한 후, 처리해야 할 작업들도 있으므로 코드가 위와 같이 간단하지는 않게 된다.

또한 메일을 분류하는 순서를 변경하고 싶을 때도 코드를 직접 수정해야 하는 문제가 발생한다.


역할 사슬 패턴 (책임 연쇄 패턴) 적용

 

역할 사슬 패턴을 적용하면 위와 같은 문제점을 해결할 수 있다.

 

이메일을 분류하는 Handler(ConcreteHandler)들은 공통된 인터페이스를 구현한다.

그리고 각 Handler가 요청을 처리하지 못하면 다음 Handler에게 요청을 전달한다.

클라이언트는 처음 요청을 처리할 Handler에게 작업을 전달한다.

 

Handler 인터페이스는 각 Handler가 처리할 요청(classify)과

요청을 처리하지 못할 경우 요청을 전달할 Handler를 설정하는 setNext를 구현하도록 한다.

class MailHandler
{
public:
	virtual void classify(const string& mail) = 0;
	virtual void setNext(MailHandler* handler) = 0;
};

 

ConcreteHandler스팸 분류기는 다음과 같이 구현할 수 있다.

class SpamMailHandler : public MailHandler
{
public:
	void setNext(MailHandler* handler) override { nextHandler = handler; }

	void classify(const string& mail) override
	{
		if (mail.find("spam") != string::npos)
			cout << "This e-mail is classified as spam." << endl;
		else
			nextHandler->classify(mail);
	}

private:
	MailHandler* nextHandler;
};

 

역할 사슬 패턴에서 요청이 반드시 수행된다는 보장이 없을 수 있다.

이 예시에서는 스팸 메일이 아니거나 중요한 메일이 아니면 모두 일반 메일함으로 전달하면 되기 때문에,

NormalMailHandlerclassify모든 작업을 처리하게 된다.

class NormalMailHandler : public MailHandler
{
public:
	void setNext(MailHandler* handler) override { nextHandler = handler; }
	void classify(const string& mail) override 
	{ cout << "This e-mail is classified as normal." << endl; }

private:
	MailHandler* nextHandler;
};

 

모든 ConcreteHandler를 생성한 후, 체인(setNext)을 형성한다. (스팸 → 중요 → 일반)

	SpamMailHandler* spamClassifier = new SpamMailHandler();
	ImportantMailHandler* importantClassifier = new ImportantMailHandler();
	NormalMailHandler* normalClassifier = new NormalMailHandler();

	spamClassifier->setNext(importantClassifier);
	importantClassifier->setNext(normalClassifier);
	normalClassifier->setNext(nullptr);

 

클라이언트최초의 작업을 수행할 핸들러를 받아 작업을 요청하면 된다.

class Client
{
public:
	void setClassifierChain(MailHandler* handler) { firstHandler = handler; }

	void classifyEmail(const string& mail)
	{
		if (firstHandler != nullptr)
			firstHandler->classify(mail);
		else
			cout << "Client : No handler configured." << endl;
	}

private:
	MailHandler* firstHandler;
};

 

전체 코드는 다음과 같다.

#include <iostream>
#include <string>

using namespace std;

class MailHandler
{
public:
	virtual void classify(const string& mail) = 0;
	virtual void setNext(MailHandler* handler) = 0;
};

class SpamMailHandler : public MailHandler
{
public:
	void setNext(MailHandler* handler) override { nextHandler = handler; }

	void classify(const string& mail) override
	{
		if (mail.find("spam") != string::npos)
			cout << "This e-mail is classified as spam." << endl;
		else
			nextHandler->classify(mail);
	}

private:
	MailHandler* nextHandler;
};

class ImportantMailHandler : public MailHandler
{
public:
	void setNext(MailHandler* handler) override { nextHandler = handler; }

	void classify(const string& mail) override
	{
		if (mail.find("important") != string::npos)
			cout << "This e-mail is classified as important." << endl;
		else
			nextHandler->classify(mail);
	}

private:
	MailHandler* nextHandler;
};

class NormalMailHandler : public MailHandler
{
public:
	void setNext(MailHandler* handler) override { nextHandler = handler; }
	void classify(const string& mail) override 
	{ cout << "This e-mail is classified as normal." << endl; }

private:
	MailHandler* nextHandler;
};

class Client
{
public:
	void setClassifierChain(MailHandler* handler) { firstHandler = handler; }

	void classifyEmail(const string& mail)
	{
		if (firstHandler != nullptr)
			firstHandler->classify(mail);
		else
			cout << "Client : No handler configured." << endl;
	}

private:
	MailHandler* firstHandler;
};

int main()
{
	SpamMailHandler* spamClassifier = new SpamMailHandler();
	ImportantMailHandler* importantClassifier = new ImportantMailHandler();
	NormalMailHandler* normalClassifier = new NormalMailHandler();

	spamClassifier->setNext(importantClassifier);
	importantClassifier->setNext(normalClassifier);
	normalClassifier->setNext(nullptr);

	Client* client = new Client();

	client->setClassifierChain(spamClassifier);

	client->classifyEmail("This is a normal mail.");
	client->classifyEmail("This is an important mail.");
	client->classifyEmail("This is a spam mail.");

	return 0;
}


ClassDiagram.md

```mermaid
  classDiagram    
    class Handler {
      successor
      handle()*
      setNext()*
    }    
    class ConcreteHandlerA {
      handle()
      setNext()
    }
    class ConcreteHandlerB {
      handle()
      setNext()    
    }
    class ConcreteHandlerC {
      handle()
      setNext()    
    }

    <<interface>> Handler

    Client --> Handler
    Handler <-- Handler
    Handler <|.. ConcreteHandlerA
    Handler <|.. ConcreteHandlerB 
    Handler <|.. ConcreteHandlerC
```
반응형