중재자는 컴포넌트 간의 커뮤니케이션을 돕기 위한 메커니즘입니다.
컴포넌트들이 서로 직접 참조하지 않고 매개자가 모든 컴포넌트들을 참조합니다.
각 컴포넌트들은 서로를 참조하지 않아도 되므로 느슨한 결합이됩니다.
채팅룸
채팅룸은 매개자 디자인 패턴의 가장 전형적인 예입니다.
Person은 메시지 수신하고, 채팅 그룹에 말을 하고, 개인 메시지 보내기를 요청 합니다.
struct Person
{
string name;
ChatRoom* room = nullptr;
vector<string> chat_log;
Person(const string& name);
void receive(const string& origin, const string& message);
void say(const string& message) const;
void pm(const string& who, const string& message) const;
};
ChatRoom에 참여하고, 모든 참여자에게 메시지를 보내고, 개인 메시지를 보냅니다.
struct ChatRoom // 중재자입니다.
{
vector<Person*> people; // People이 서로의 참조를 모두 가지는 것이 아니라 ChatRoom이 People을 모두 가지고 있습니다.
void join(Person* p);
void broadcast(const string& origin, const string& message);
void message(const string& origin, const string& who, const string& message);
};
중재자와 이벤트
누군가 메시지를 올리면 참여자들은 알림을 받아야 합니다.
C++에서는 언어 차원에서 이벤트 기능을 제공하지 않습니다. Boost.Signal2 라이브러리를 이용합니다.(앞, 연쇄 책임 패턴에서 살펴 보았습니다.)
Signal2에서는 이벤트를 시그널(알림을 발생시키는 객체), 수신처를 슬롯(알림을 처리하는 함수)으로 부릅니다.
이번에는 채팅룸 보다 좀더 단순한 축구 게임 예를 살펴보겠습니다.
이벤트를 일반화 하는 클래스 입니다.
struct EventData
{
virtual ~EventData() = default;
virtual void print() const = 0;
};
이 클래스를 상속받아 골 득점 정보를 저장하는 클래스를 만들 수 있습니다.
struct PlayerScoredData : EventData
{
string player_name;
int goals_scored_so_far;
PlayerScoredData(const string& player_name, const int goals_scored_so_far)
: player_name(player_name), goals_scored_so_far(goals_scored_so_far) {}
void print() const override
{
cout << player_name << " has scored! (their "
<< goals_scored_so_far << " goal)" << "\n";
}
};
중재자를 만듭니다. 아무런 기능이 없으며 중재자가 직접 일을 수행하지 않습니다.
struct Game
{
signal<void(EventData*)> events; // 중재자(Mediator)
};
Player 클래스를 만듭니다. Player는 선수 이름, 게임에서의 골 득점 그리고 중재자인 Game의 참조를 가집니다.
struct Player
{
string name;
int goals_scored = 0;
Game& game;
Player(const string& name, Game& game)
: name(name),game(game) {}
void score()
{
goals_scored++;
PlayerScoredData ps{name, goals_scored};
game.events(&ps); // 수신처로 등록된 객체들에게 알림을 전송
}
};
Player::score 함수는 이벤트를 이용해 PlayerScoredData를 생성하고 모든 이벤트의 수신처로 등록된 객체들에 알림을 전송합니다.
골 득점 이벤트를 받는 Coach 클래스를 만듭니다.
struct Coach
{
Game& game;
explicit Coach(Game& game) : game(game)
{
game.events.connect // 이벤트 수신을 등록
([](EventData* e)
{
PlayerScoredData* ps = dynamic_cast<PlayerScoredData*>(e);
if (ps && ps->goals_scored_so_far < 3) // 2번째 골까지만 코치가 반응 합니다.
{
cout << "coach says: well done, " << ps->player_name << "\n";
}
});
}
};
Coach 클래스는 game.events에 수신을 등록할 뿐입니다.
game에서 어떤 이벤트가 발생하면 알림을 받게 되어 필요한 처리를 할 수 있습니다.
아래는 지금까지 작성한 코드를 실행한 예입니다.
Game game;
Player player{ "Sam", game };
Coach coach{ game };
player.score();
player.score();
player.score(); // 코치가 무시 합니다.
요약
중재자 디자인 패턴은 시스템 내 컴포넌트 모두가 참조할 수 있는 어떤 중간자를 컴포넌트 간에 서로 직접 참조하지 않더라도 커뮤니케이션을 할 수 있게합니다.
중재자의 단순한 형태는 채팅룸 예와 같이 중재해야할 대상의 목록을 두고 그 리스트를 검사하여 필요한 항목만 선택적으로 처리하는 것입니다.
중재자의 좀더 정교한 구현은 축구 게임과 같이 시스템에서 발생하는 이벤트들에 대해서 이벤트를 받길 원하는 객체가 개별적으로 수신 등록을 할 수 있게 하는 것입니다.
코드 참고 : https://cpp-design-patterns.readthedocs.io/en/latest/
'프로그래밍 일반 > 디자인 패턴' 카테고리의 다른 글
메멘토 패턴(Memento Pattern) (0) | 2020.03.04 |
---|---|
커맨드 패턴(Command Pattern) (0) | 2020.03.04 |
반복자 패턴(Iterator Pattern) (0) | 2020.03.04 |
책임 사슬(Chain of Responsibility Pattern) (0) | 2020.03.04 |
프락시 패턴(Proxy Pattern) (0) | 2020.03.04 |