역할 사슬 패턴이란? |
CoR패턴, 역할 사슬 패턴, 책임 사슬 패턴, 책임 연쇄 패턴 등으로 불림
1) 어려운 설명
요청을 하는 객체와, 요청을 처리하는 여러종류의 객체들 사이에 역할 사슬을 구성하는 패턴
2) 쉬운 설명
A형식의 요청을 처리하는 객체A, B형식의 요청을 처리하는 객체 B, C형식의 요청을 처리하는 객체 C가 있을 때
Client가 먼저 객체A에게 일을 시켜보고 본인에게 맞지 않는 일이라면 객체A가 객체B에게 일을 넘기고, 객체B도 본인에게 맞지 않는 일이라면 객체C에게 일을 넘기는 방식
역할 사슬 패턴의 구조 |
1) Handler : 인터페이스 제공
- setNext() : 역할 사슬을 만드는 함수로, 본인 다음에 역할을 맡을 객체를 지정하는 함수
- handle() : 본인이 처리할 수 있는 요청이라면 어떤식으로 처리할 지 작성, 만약 처리할 수 없는 요청이라면 setNext()를 통해 지정된 객체의 handle()을 호출
2) ConcreteHandler
- 인터페이스를 상속받아 실제로 요청을 처리하는 객체들
역할 사슬 패턴을 사용하지 않는다면? |
메일이 왔을때 내용에 따라 스팸/중요/일반 메일함 중 한곳으로 분류한다고 가정한다면 아래 코드처럼 구현할 수 있다.
#include <iostream>
using namespace std;
class MailBox
{
public:
void handle(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;
}
};
void main()
{
MailBox* mailbox = new MailBox();
mailbox->handle("This is a normal mail.");
mailbox->handle("This is an important mail.");
mailbox->handle("This is a spam mail.");
}
결과)
하지만 위처럼 구현할 경우 새로운 메일함이 추가된다면 MailBox 클래스를 변경해야 하므로 OCP원칙에 위배되며
메일을 분류하는 순서를 스팸 -> 중요 -> 일반이 아닌 다른 순서로 변경하고 싶다고 해도 클래스의 내용을 변경해야 하고,
위처럼 단지 cout으로 출력하는것뿐 아니라 실제로 메일함 시스템에서 파일을 옮기는 등 복잡한 과정을 거쳐야 한다면 하나의 클래스가 여러 메일함의 기능을 모두 수행하므로 단일책임원칙도 위배하게 될 수 있다.
역할 사슬 패턴을 사용한다면 |
#include <iostream>
using namespace std;
class MailBoxHandler
{
public:
virtual void handle(const string& mail) = 0;
virtual void setNext(MailBoxHandler* handler) = 0;
};
class SpamMailBoxHandler : public MailBoxHandler
{
private:
MailBoxHandler* nextHandler;
public:
void setNext(MailBoxHandler* handler) override { nextHandler = handler; }
void handle(const string& mail) override {
if (mail.find("spam") != string::npos)
cout << "This e-mail is classified as spam." << endl;
else
if(nextHandler != nullptr) nextHandler->handle(mail);
}
};
class ImportantMailBoxHandler : public MailBoxHandler
{
private:
MailBoxHandler* nextHandler;
public:
void setNext(MailBoxHandler* handler) override { nextHandler = handler; }
void handle(const string& mail) override {
if (mail.find("important") != string::npos)
cout << "This e-mail is classified as important." << endl;
else
if (nextHandler != nullptr) nextHandler->handle(mail);
}
};
class NormalMailBoxHandler : public MailBoxHandler
{
private:
MailBoxHandler* nextHandler;
public:
void setNext(MailBoxHandler* handler) override { nextHandler = handler; }
void handle(const string& mail) override {
if (mail.find("normal") != string::npos)
cout << "This e-mail is classified as normal." << endl;
else
if (nextHandler != nullptr) nextHandler->handle(mail);
}
};
class Client
{
private:
MailBoxHandler* firstHandler;
public:
void setClassifierChain(MailBoxHandler* handler) { firstHandler = handler; }
void handleEmail(const string& mail) {
if (firstHandler != nullptr) firstHandler->handle(mail);
else cout << "Client : No handler configured." << endl;
}
};
void main()
{
SpamMailBoxHandler* spamClassifier = new SpamMailBoxHandler();
ImportantMailBoxHandler* importantClassifier = new ImportantMailBoxHandler();
NormalMailBoxHandler* normalClassifier = new NormalMailBoxHandler();
spamClassifier->setNext(importantClassifier);
importantClassifier->setNext(normalClassifier);
normalClassifier->setNext(nullptr);
Client* client = new Client();
client->setClassifierChain(spamClassifier);
client->handleEmail("This is a normal mail.");
client->handleEmail("This is an important mail.");
client->handleEmail("This is a spam mail.");
}
main함수부분을 잘 보면
1) 스팸/중요/일반 메일함을 처리하는 객체 생성
2) 각 객체는 자신의 다음에 역할을 맡을 객체를 지정
3) Client는 첫번째로 역할을 맡을 객체만 지정
4) Client로 요청 날리면 자동으로 해당 역할 사슬을 돌며 특정 객체가 역할을 수행하게 됨
역할 사슬 패턴의 장단점 |
1) 장점
- 단일 책임 원칙 준수 : 각 객체가 각자의 역할만을 수행하며, 요청을 처리하는 객체와 처리하는 객체도 분리된다.
- 개방 폐쇄 원칙 준수 : 새로운 메일함이 생겨도 새로운 클래스를 만들면 될 뿐 기존 클래스를 변경하지 않아도 된다.
- 요청 처리 순서를 런타임에 변경할 수 있고, 클라이언트는 누가 요청을 처리하는지 몰라도 된다.
2) 단점
- 요청이 결국 모든 객체를 거쳤는데도 처리되지 않은 경우에 대한 처리가 필요하다.
위 예시에서는 처리과정들에 걸리지 않으면 결국 일반메일함으로 들어가게 구성되었다.
- 클라이언트가 명시적으로 요청을 처리할 Handler를 지정할 수 없고 요청이 전달되는 과정에서 오버헤드가 생길 수 있다.
※ 참고 문헌