- 객체지향의 특징은 **단일 책임 원칙(SRP)**에 의해 하나의 객체는 하나의 책임(행동)을 갖습니다.
- 또한 클래스 재사용을 극대화하기 위해 작은 클래스를 많이 만들게 됩니다.
- 객체지향 프로그램은 다수의 작은 클래스간의 결합을 통해 하나의 행동을 합니다. 즉 작은 행동들이 모여 큰 하나의 행동을 만듭니다.
- 하지만 다수의 클래스가 다수의 클래스와의 관계가 이어지면 복잡한 결합 관계로 성장합니다.
- 중재자 패턴은 다수의 클래스 간에 설정된 복잡한 관계를 느슨한 관계로 개선하기 위한 패턴입니다.
- 한 모임에는 총무 역할의 단일 사람이 있고, 한 반에는 단일 사람의 반장이 있는 것과 비슷합니다.
- 총무는 다수의 모임 구성원들과 다수의 가게들과의 관계를 중재하는 중재자입니다.
- 반장은 다수의 학급 구성원들과 다수의 선생님들과의 관계를 중재하는 중재자입니다.
중재자 패턴 구조#
- 다수의 클래스 A는 다수의 클래스 B와의 관계를 설정합니다. 이를 M : N 관계라고 합니다.
- 중재자는 A와 B 사이에 개입하여 1 : 1 또는 1 : M 관계로 느슨하게 조정합니다.
- 중재자 패턴은 하나의 중재자와 다수의 동료 객체로 구성되어 있습니다.
- Mediator
- ConcreteMediator
- Colleague
- ConcreteColleague
- 중재자 패턴 예제로 많이 사용하는 채팅 프로그램을 작성해보겠습니다.
- 중재자 패턴은 관찰자 패턴(Observer Pattern)과 유사한 부분이 있습니다.
- 중재자 패턴 또한 동료 객체를 추가, 삭제하는 메서드를 갖고 있습니다.
- 또한 채팅 프로그램의 기능으로 다른 객체들에게 메세지를 다시 전달할
sendMessage()
메서드가 있습니다. - 모두 가상 함수로 구성하여 인터페이스 클래스를 선언합니다.
// Mediator
class Mediator
{
public:
virtual void addUser(std::shared_ptr<Colleague> colleague) = 0;
virtual void deleteUser(std::shared_ptr<Colleague> colleague) = 0;
virtual void sendMessage(std::string message, std::shared_ptr<Colleague> sender) = 0;
};
중재자 패턴: 동료(Colleague) 인터페이스 클래스#
Mediator
클래스를 구현하기 전 동료 객체의 인터페이스 클래스인 Colleague
클래스를 선언합니다.- 메세지를 전달하고, 수신하는 기능들로 구성되어 있으며 어려운 내용은 없습니다.
- 다만
Mediator
클래스와 Colleague
클래스는 상호 참조 관계로 구성되어 있습니다.
// Colleague
class Colleague
{
public:
Colleague(std::shared_ptr<Mediator> mediator, std::string name)
{
m_mediator = mediator;
m_name = name;
}
virtual void sendMessage(std::string message) = 0;
virtual void recvMessage(std::string message) = 0;
protected:
std::shared_ptr<Mediator> m_mediator;
std::string m_name;
};
중재자 패턴: 중재자 클래스 구현#
- 관찰자 패턴에서 사용한 구조와 매우 유사합니다.
- 동료 객체를 내부 컨테이너에 추가&삭제하는 기능을 구현합니다.
// ConcreteMediator
class chatMediator :
public Mediator
{
public:
void addUser(std::shared_ptr<Colleague> colleague) override
{
m_colleagues.push_back(colleague);
}
void deleteUser(std::shared_ptr<Colleague> colleague) override
{
for (auto iter = m_colleagues.begin(); iter != m_colleagues.end(); iter++)
{
if (*iter == colleague)
{
m_colleagues.erase(iter);
break;
}
}
}
void sendMessage(std::string message, std::shared_ptr<Colleague> sender) override
{
for (const auto& colleague : m_colleagues)
{
if (colleague != sender)
{
colleague->recvMessage(message);
}
}
}
private:
std::vector<std::shared_ptr<Colleague>> m_colleagues;
};
중재자 패턴: 동료 클래스 구현#
- 상호 참조 관계로 인해
C
또는 C++
에서 많이 사용하던 this
포인터가 필요합니다. Morden C++
에서는 스마트 포인터를 사용하기 때문에 shared_ptr<T>
를 this
포인터로 변환하기 위한 STL
클래스를 상속합니다.shared_from_this()
을 사용하면 this
포인터를 shared_ptr<T>
타입으로 전달할 수 있습니다.- 그치만 다중 상속을 하는 경우에는 복잡한 생성자의 관계로 인해 버그가 발생할 수 있습니다.
shared_from_this()
는 생성자에서 호출하는 경우 this
포인터가 만들어지지 않기 때문에 Null-Pointer Exception
이 발생합니다.- 이를 해결하는 방법으로
lambda
함수를 사용하여 캐스팅하는 방법이 있습니다.
// ConcreteColleague
class User :
public Colleague,
public std::enable_shared_from_this<User>
{
public:
User(std::shared_ptr<Mediator> mediator, std::string name) :
Colleague(mediator, name)
{
}
void sendMessage(std::string message)
{
std::cout << m_name + " [send] : " << message << std::endl;
m_mediator->sendMessage(message, shared_from_this());
}
void recvMessage(std::string message)
{
std::cout << m_name + " [recv] : " << message << std::endl;
}
};
중재자 패턴: 실행 코드#
int main(const int argc, const char* argv[])
{
std::shared_ptr<chatMediator> chat = std::make_shared<chatMediator>();
std::shared_ptr<User> kim = std::make_shared<User>(chat, "kil");
std::shared_ptr<User> lee = std::make_shared<User>(chat, "lee");
std::shared_ptr<User> park = std::make_shared<User>(chat, "park");
chat->addUser(kim);
chat->addUser(lee);
chat->addUser(park);
kim->sendMessage("Hello, I'm Kim");
return 0;
}
kil [send] : Hello, I'm Kim
lee [recv] : Hello, I'm Kim
park [recv] : Hello, I'm Kim
- 통신을 위한 높은 레벨의 인터페이스를 제공한다는 점에서 중재자 패턴과 파사드 패턴은 비슷합니다.
- 하지만 중재자 패턴은 양방향 통신이 가능하고, 파사드 패턴은 단방향 통신만 가능하다는 점에서 차이가 있습니다.