중재자 패턴
객체 간의 복잡한 상호작용을 캡슐화하는 디자인 패턴. 객체들 사이의 직접적인 참조와 통신을 줄이고, 이를 대신하여 중재자 객체를 통해 상호작용하도록 함으로써 객체 간의 결합도를 낮춘다.
구조
- Mediator - 각 객체간 상호작용에 필요한 인터페이스를 정의한다.
- ConcreteMediator - Colleague 객체간의 협력 행동을 구현한다. 자신이 맡은 Colleague를 파악하고 관리한다.
- Colleague - 시스템 내의 객체들로, 서로 직접적으로 통신하지 않고 중재자를 통해 상호작용한다. 각 객체는 중재자에 대한 참조를 가지고 있어야 한다.
기본 인터페이스
public interface IMediator
{
void SendMessage(string message, Colleague colleague);
}
public abstract class Colleague
{
protected IMediator mediator;
public Colleague(IMediator mediator)
{
this.mediator = mediator;
}
public virtual void Send(string message)
{
mediator.SendMessage(message, this);
}
public abstract void Receive(string message);
}
public class ConcreteColleague1 : Colleague
{
public ConcreteColleague1(IMediator mediator) : base(mediator) { }
public override void Receive(string message)
{
Console.WriteLine("Colleague1 received message: " + message);
}
}
public class ConcreteColleague2 : Colleague
{
public ConcreteColleague2(IMediator mediator) : base(mediator) { }
public override void Receive(string message)
{
Console.WriteLine("Colleague2 received message: " + message);
}
}
public class ConcreteMediator : IMediator
{
private ConcreteColleague1 colleague1;
private ConcreteColleague2 colleague2;
public void SetColleague1(ConcreteColleague1 colleague)
{
colleague1 = colleague;
}
public void SetColleague2(ConcreteColleague2 colleague)
{
colleague2 = colleague;
}
public void SendMessage(string message, Colleague colleague)
{
if (colleague == colleague1)
{
colleague2.Receive(message);
}
else
{
colleague1.Receive(message);
}
}
}
class Program
{
static void Main(string[] args)
{
ConcreteMediator mediator = new ConcreteMediator();
ConcreteColleague1 colleague1 = new ConcreteColleague1(mediator);
ConcreteColleague2 colleague2 = new ConcreteColleague2(mediator);
mediator.SetColleague1(colleague1);
mediator.SetColleague2(colleague2);
colleague1.Send("Hello, World!");
colleague2.Send("Hi there!");
}
}
왜 사용해야 할까?
중재자 패턴은 여러 객체 간의 소통 방법을 추상화함으로써, 객체들 사이의 의존성을 낮추는 디자인 패턴이다.
이 패턴을 적용하지 않은 상태에서는 여러 객체들이 서로 직접 소통하게 되어 있다. 이러한 직접적인 소통은 객체들 간의 강한 결합을 초래하며, 이는 기존 코드의 변경, 테스트, 재사용을 어렵게 만든다.
이를 공항의 비행기 이착륙 상황에 비유할 수 있다. 공항에서는 여러 비행기들이 이착륙을 해야 하며, 이 과정에서 비행기들 간의 충돌을 피하기 위해 서로 의사소통이 필요하다. 그러나 실제로 비행기들은 서로 직접 소통하지 않는다. 대신, 이착륙과 관련된 모든 소통은 관제탑을 통해 이루어진다. 관제탑은 비행기들 간의 의사소통을 조정하고 관리하여 각 비행기가 안전하게 이착륙할 수 있도록 돕는다.
중재자 패턴은 이와 유사하게 작동한다. 여러 객체들이 서로 직접적으로 소통하는 대신, 중재자 객체를 통해 소통을 진행한다. 이렇게 함으로써 각 객체는 중재자에만 의존하며, 다른 객체들과는 직접적인 관계를 가지지 않는다. 결과적으로 이 패턴은 객체들 사이의 결합도를 낮추고, 시스템의 유지보수와 확장성을 개선하는 데 도움을 준다.
간단한 예시로 카카오톡에서의 채팅 시스템을 생각할 수 있다. 카카오톡은 1 대 1로 대화할 수도 있지만, 다수의 사람과 동시에 소통할 수 있게 설계되어 있다. 즉, 사용자가 메시지를 통신하면 같은 방에 있는 모든 사용자에게 메시지가 보내지는 방식이다.
이런 시스템에서 사용자가 다른 사용자를 직접 알고 있는 방식은 좋지 않다. 채팅방의 사용자의 숫자나 사람이 언제든지 바뀔 가능성이 있으며, 각각의 설정에 따라 어떤 메시지는 받지 않는다던지 다른 결과를 제공할 필요가 있기 때문이다. 사용자가 각 사용자에게 직접 접근한다면 이런 문제들을 사용자가 직접 해결해야 하기 때문에 매번 사용자(User) 코드가 변경되고 유지보수가 어려워질 수 있다.
이를 방지하기 위해 각 채팅방에 채팅 관리자 객체를 생성하는 것이 좋다. 채팅 관리자는 사용자들의 정보를 관리한다. 사용자들은 다른 사용자와 직접적으로 통신하지 않고 자신의 메시지를 채팅 관리자에게 제공하면, 채팅 관리자가 다른 사용자들에게 메시지를 전달하는 방식을 중재할 수 있다.
1. 중재자 인터페이스 정의: IChatRoomMediator
public interface IChatRoomMediator
{
void SendMessage(string message, User user);
void RegisterUser(User user);
}
2. 중재자 구현: ChatRoom
public class ChatRoom : IChatRoomMediator
{
private readonly Dictionary<string, User> _users = new Dictionary<string, User>();
public void RegisterUser(User user)
{
if (!_users.ContainsValue(user))
{
_users[user.Name] = user;
}
user.ChatRoom = this;
}
public void SendMessage(string message, User user)
{
foreach (var u in _users.Values)
{
if (u != user)
{
u.Receive(message, user);
}
}
}
}
3. 사용자 클래스 정의: User
public class User
{
public string Name { get; }
public IChatRoomMediator ChatRoom { get; set; }
public User(string name)
{
Name = name;
}
public void Send(string message)
{
Console.WriteLine($"{this.Name}: Sending Message = {message}");
ChatRoom.SendMessage(message, this);
}
public void Receive(string message, User sender)
{
Console.WriteLine($"{this.Name}: Received Message from {sender.Name}: {message}");
}
}
4. 메인 코드 결과
class Program
{
static void Main(string[] args)
{
ChatRoom chatRoom = new ChatRoom();
User user1 = new User("John");
User user2 = new User("Jane");
User user3 = new User("Bob");
chatRoom.RegisterUser(user1);
chatRoom.RegisterUser(user2);
chatRoom.RegisterUser(user3);
user1.Send("Hi there!");
user2.Send("Hey!");
user3.Send("Hello everyone!");
}
}
John: Sending Message = Hi there!
Jane: Received Message from John: Hi there!
Bob: Received Message from John: Hi there!
Jane: Sending Message = Hey!
John: Received Message from Jane: Hey!
Bob: Received Message from Jane: Hey!
Bob: Sending Message = Hello everyone!
John: Received Message from Bob: Hello everyone!
Jane: Received Message from Bob: Hello everyone!
장점
- 객체들 사이의 결합도를 줄이고, 상호작용 로직을 중앙에서 관리함으로써 시스템의 유지보수와 확장성을 개선한다.
- 체시스템 내의 객체들이 더 독립적이며 재사용 가능해진다.
- 통신 패턴을 이해하고 수정하기가 더 쉬워진다.
단점
- 중재자 자체가 시스템의 복잡성을 흡수하게 되어 매우 복잡해져 유지보수가 어려워질 수 있다.
- 중재자가 병목 지점(한 곳으로 집중되는 곳)이 되어 성능 저하가 발생할 가능성이 있다.
참고자료
GoF의 디자인 패턴 - 예스24
이 책은 디자인 패턴을 다룬 이론서로 디자인 패턴의 기초적이고 전반적인 내용을 학습할 수 있다.
www.yes24.com
'프로그래밍 이론 > 디자인 패턴' 카테고리의 다른 글
20. [Design Pattern] 상태 패턴(State) (0) | 2024.02.20 |
---|---|
19. [Design Pattern] 메멘토(Memento) 패턴 (0) | 2024.02.19 |
17. [Design Pattern] 전략(Strategy) 패턴 (0) | 2024.01.17 |
16. [Design Pattern] 이터레이터(Iterator) 패턴 (0) | 2024.01.14 |
15. [Design Pattern] 인터프리터(interpreter) 패턴 (0) | 2024.01.14 |