의도

  • 한 집합에 속해있는 객체의 상호작용을 캡슐화하는 객체를 정의함. 객체들이 직접 서로를 참조하지 않도록 하여 객체 사이의 소결합을 촉진시키며, 개발자가 상호작용을 독립적으로 다양화시킬 수 있게 만듬

 

언제 쓰는가 ?

  • 여러 객체가 잘 정의된 형태이기는 하지만 복잡합 상호작용을 가질 때. 객체간의 의존성이 구조화되지 않으며, 잘 이해하기 어려울 때.
  • 한 객체가 다른 객체를 너무 많이 참조하고, 너무 많은 의사소통을 수행해서 그 객체를 재사용하기 힘들 때.
  • 여러 클래스에 분산된 행동들이 상속 없이 상황에 맞게 수정되어야 할 때.

 

구조

GoF의 디자인 패턴 중 P.363
GoF의 디자인 패턴 중 P.363

  • Mediator : Colleague 객체와 교류하는 데 필요한 인터페이스를 정의함.
  • ConcreteMeditator : Colleague 객체와 조화를 이뤄서 협력 행동을 구현하며, 자신이 맡을 동료를 파악하고 관리함.
  • Colleague 클래스들 : 자신의 중재자 객체가 무엇인지 파악함. 다른 객체와 통신이 필요하면 그 중재자를 통해 통신되도록 하는 동료 객체를 나타내는 클래스.

 

협력 방법

  • Colleague는 Mediator에서 요청을 송수신함. Mediator는 필요한 Colleague 사이에 요청을 전달할 의무가 있음.

 

결과

  1. 서브클래싱을 제한함
    1. 중재자는 다른 객체 사이에 분산된 객체의 행동들을 하나의 객체에 국한함. 이 행동을 변경하고자 한다면 Mediator 클래스를 상속하는 서브클래스만 만들면 됨.
  2. Colleague 객체 사이의 종속성을 줄임
    1. 중재자는 행동에 참여하는 객체간의 소결합을 증진시킴. 이로써 Mediator 클래스와 Colleague 클래스 각각을 독립적으로 다양화시킬 수 있고 재사용할 수 있음.
  3. 객체 프로토콜을 단순화함
    1. 중재자는 다대일, 일대다의 관계로 축소시킴. 이해가 쉬워지고 유지, 확장이 쉬워짐.
  4. 객체 간의 협력 방법을 추상화함
    1. 객체 간의 독립적인 개념으로 만들고 이것을 캡슐화함으로써, 사용자는 각 객체의 행동과 상관없이 객체 간 연결 방법에만 집중할 수 있음.
  5. 통제가 집중화됨
    1. 중재자 패턴은 상호작용의 복잡합 모든 것들이 자신 내부에서만 오가게 함. 중재자 객체는 동료 객체 간의 상호작용에 관련된 프로토콜을 모두 캡슐화하기 때문에 어느 동료 객체보다도 훨씬 복잡해질 수 있음. 이 때문에 Mediator 클래스 자체의 유지보수가 어려워질 때가 있음.

 

구현 / 고려 사항

  1. 추상 클래스인 Mediator 생략
    1. 추상 클래스는 또 다른 상호작용을 정의할 새로운 Mediator 클래스를 대비하는 것. 굳이 안 만들어도 됨.
  2. 동료 객체 - 중재자 객체 간 의사소통
    1. 중재자를 구현하는 방법으로 감시자 패턴을 사용하는 방법이 있음(구독, 발행)
    2. Mediator 클래스 내에 통지(Notification) 인터페이스를 정의하여 동료 객체들이 직접 통신하게 하는 방법이 있음

 

예제 코드

  • 책의 예제에선 Widget에 대한 Director를 두어 Widget 간 통신을 Director에게 맡기는 구성을 하였습니다.
  • 저는 축구 경기를 코드로 작성해보았고, 경기를 진행함에 있어 심판을 Director 혹은 Mediator로 두었고 선수들을 Player로 칭하며 이들을 Mediator의 중재를 받아 특정 처리를 할 수 있게 구성하였습니다.
#include "Mediator1.h"

int main()
{
	EASoccerMediator* ea_soccer_mediator = new EASoccerMediator;

	Player* player1 = new Player("Son", ea_soccer_mediator, Team::A);
	Player* player2 = new Player("Lee", ea_soccer_mediator, Team::A);

	Player* player3 = new Player("Park", ea_soccer_mediator, Team::B);
	Player* player4 = new Player("Hwang", ea_soccer_mediator, Team::B);

	ea_soccer_mediator->Register(player1);
	ea_soccer_mediator->Register(player2);
	ea_soccer_mediator->Register(player3);
	ea_soccer_mediator->Register(player4);

	ea_soccer_mediator->Foul(player3);
	ea_soccer_mediator->Foul(player2);
	ea_soccer_mediator->Foul(player3);

	Player* player5 = new Player("Ki", ea_soccer_mediator, Team::B);

	ea_soccer_mediator->ChangePlayer(player5, player4);

	return 0;
}

  • 중재자인 ea_soccer_mediator(유럽 축구 심판이라는 의미 ㅋㅋ)가 있고 심판을 통해 각 선수들(Player)를 Register 하게 되면 경기를 뛸 수 있게 됩니다.
  • 이 때 mediator를 통해 Foul도 할 수 있고 경기를 뛰고 있는 선수를 Change 할 수도 있습니다.
//Mediator1.h

#pragma once
#include <set>
#include <string>

class Player;

enum class Team
{
	A,
	B
};

__interface SoccerMediator
{
	void Foul(Player* player);
	void ChangePlayer(Player* in_player, Player* out_player);
	void Register(Player* player);
	void UnRegister(Player* player);
};

class EASoccerMediator : public SoccerMediator
{
public:
	virtual void Foul(Player* player) override;
	virtual void ChangePlayer(Player* in_player, Player* out_player) override;
	virtual void Register(Player* player) override;
	virtual void UnRegister(Player* player) override;

private:
	std::set<Player*> TeamA;
	std::set<Player*> TeamB;
};

class Player
{
public:
	Player(std::string name, SoccerMediator* soccer_mediator, Team team)
		: name(name), soccer_mediator(soccer_mediator), team(team)
	{}

	void Register();
	void UnRegister();
	void Foul();
	Team GetTeam() { return team; }

private:
	std::string name;
	SoccerMediator* soccer_mediator;
	bool bOnPlaying = false;
	Team team;
	size_t foul_count = 0;
};
  • SoccerMediator라는 축구 심판의 공통 인터페이스를 만들었고, EASoccerMediator는 SoccerMediator를 상속받아 TeamA와 TeamB를 추가로 관리하게 됩니다.
  • 선수는 현재 경기를 뛰고 있는지의 여부를 관리하는 bOnPlaying, 속한 팀이 어디인지를 나타내는 team, 파울을 한 횟수가 몇 번인지를 나타내는 foul_count를 직접 관리하게 되고, 이 때 이 패턴의 핵심은 player가 soccer_mediator를 알고 있다는 점입니다. soccer_mediator를 알고 있음으로서 soccer_mediator의 메서드를 호출할 수 있거나 mediator를 통해 다른 동료들(Player)에게도 접근할 수 있게 됩니다.
//Mediator1.cpp

#include "Mediator1.h"
#include <iostream>

void Player::Register()
{
	bOnPlaying = true;
	std::cout << name << " 선수가 경기장에 입장합니다 ! " << std::endl;
}

void Player::UnRegister()
{
	bOnPlaying = false;
	std::cout << name << " 선수가 경기장에서 퇴장합니다 ! " << std::endl;
}

void Player::Foul()
{
	++foul_count;
	
	if (foul_count >= 2)
	{
		soccer_mediator->UnRegister(this);

		std::cout << name << " 선수가 퇴장 당했습니다 ! " << std::endl;
	}
	else
	{
		std::cout << name << " 선수가 옐로 카드를 받았습니다 ! " << std::endl;
	}
}

void EASoccerMediator::Foul(Player* player)
{
	player->Foul();
}

void EASoccerMediator::ChangePlayer(Player* in_player, Player* out_player)
{
	if (out_player->GetTeam() == Team::A)
	{
		if (TeamA.find(out_player) != TeamA.end())
		{
			Register(in_player);
			UnRegister(out_player);
		}
	}
	else if (out_player->GetTeam() == Team::B)
	{
		if (TeamB.find(out_player) != TeamB.end())
		{
			Register(in_player);
			UnRegister(out_player);
		}
	}
}

void EASoccerMediator::Register(Player* player)
{
	if (player->GetTeam() == Team::A)
	{
		TeamA.insert(player);
		player->Register();
	}
	else if (player->GetTeam() == Team::B)
	{
		TeamB.insert(player);
		player->Register();
	}
}

void EASoccerMediator::UnRegister(Player* player)
{
	if (player->GetTeam() == Team::A)
	{
		TeamA.erase(player);
		player->UnRegister();
	}
	else if (player->GetTeam() == Team::B)
	{
		TeamB.erase(player);
		player->UnRegister();
	}
}
  • 중재자를 통해 특정 선수에게 Foul 처리를 할 수 있게 되고 이는 Player의 Foul 메서드를 호출하여 본인의 foul_count를 늘리고 2번 이상 파울했다면 다시 중재자의 UnRegister를 호출하여 경기에서 제외됩니다.
  • 마찬가지로 경기에 뛸 Player를 Change하게 될 경우 중재자의 ChangePlayer 메서드를 호출하여 처리하게 됩니다.

'디자인 패턴 > 행위' 카테고리의 다른 글

감시자(Observer)  (0) 2024.01.12
메멘토(Memento)  (1) 2024.01.11
반복자(Iterator)  (1) 2024.01.04
해석자(Interpreter)  (1) 2024.01.04
명령(Command)  (4) 2024.01.03

+ Recent posts