Mediator

Mediator Pattern이란?

미디에이터 패턴Mediator Pattern은 객체들 간의 상호작용을 캡슐화하여, 객체들이 직접적으로 서로를 참조하지 않고 상호작용할 수 있도록 중재자를 두는 패턴입니다. 이 패턴을 통해 객체 간의 복잡한 의존성을 줄이고, 객체 간 통신을 간단하게 처리할 수 있습니다.

Mediator Pattern의 필요성

미디에이터 패턴은 다음과 같은 상황에서 필요합니다:

객체 간의 복잡한 의존성 관리

여러 객체가 서로 의존하고 상호작용하는 복잡한 구조에서는 각 객체의 변경이 다른 객체에 영향을 미칩니다. 이러한 구조에서는 의존성을 관리하는 것이 어렵고, 유지보수가 어려워집니다. 미디에이터 패턴은 이를 해결하기 위해 각 객체가 미디에이터와만 상호작용하도록 하여 객체 간의 의존성을 줄입니다.

통신의 중앙 집중화

여러 객체가 서로 통신하는 구조에서는 커뮤니케이션이 분산되어 관리하기 어려워질 수 있습니다. 미디에이터 패턴은 통신을 중앙에서 관리함으로써 이 문제를 해결합니다.

Mediator Pattern 구조

D2 Diagram

  • 클라이언트Client → Mediator
    • 클라이언트는 Mediator에게 작업 요청을 전달합니다(Request Action).
    • 이를 통해 클라이언트는 직접적으로 다른 동료Colleague와 상호 작용하지 않고, Mediator를 통해 통신합니다.
  • Mediator → ConcreteColleagueA / ConcreteColleagueB
    • Mediator는 요청을 처리하기 위해 관련된 특정 동료(ConcreteColleagueA, ConcreteColleagueB)에게 알립니다(Notify).
    • 이 과정에서 Mediator는 각 동료에게 필요한 작업을 위임합니다.
  • ConcreteColleagueA / ConcreteColleagueB → Mediator
    • 각 동료는 자신의 작업이 완료되거나 상태가 변경되면 Mediator에게 업데이트를 전달합니다(Communicate Update).
    • 이를 통해 Mediator는 전체 시스템의 상태를 관리하고 조정할 수 있습니다.

Mediator Pattern의 구성 요소

미디에이터 패턴은 다음과 같은 구성 요소로 이루어집니다:

미디에이터Mediator

객체 간의 상호작용을 조정하는 인터페이스를 정의합니다.

구체적인 미디에이터Concrete Mediator

미디에이터 인터페이스를 구현하며, 여러 객체 간의 상호작용을 처리하는 역할을 합니다.

동료 객체Colleague

미디에이터를 통해 다른 객체와 통신하는 객체입니다. 동료 객체는 미디에이터와만 직접 상호작용하며, 다른 동료 객체들과는 간접적으로 상호작용합니다.

Mediator Pattern 적용

잘못된 객체 간 상호작용 처리 방식

미디에이터 패턴을 적용하지 않으면, 여러 객체가 직접적으로 서로 참조하고 상호작용하는 복잡한 구조가 될 수 있습니다. 예를 들어, 도서 관리 시스템에서 여러 객체가 서로 상호작용하는 복잡한 예시입니다.

// 여러 객체가 서로 직접 참조하는 방식
public class Catalog
{
    private Notification _notification;
    public Catalog(Notification notification)
    {
        _notification = notification;
    }
    public void AddBook(string book)
    {
        Console.WriteLine($"Book '{book}' added to catalog.");
        _notification.SendNotification($"New book '{book}' added.");
    }
}
public class Notification
{
    public void SendNotification(string message)
    {
        Console.WriteLine($"Notification: {message}");
    }
}
public class MemberManagement
{
    private Notification _notification;
    public MemberManagement(Notification notification)
    {
        _notification = notification;
    }
    public void RegisterMember(string member)
    {
        Console.WriteLine($"Member '{member}' registered.");
        _notification.SendNotification($"Welcome, {member}!");
    }
}

객체 간의 의존성 증가

Catalog, MemberManagement, Notification 클래스가 서로 직접 참조하면서 강한 결합이 형성됩니다. 이는 객체 간의 의존성이 높아져 유지보수와 확장성이 떨어집니다.

Mediator Pattern 적용 예시

미디에이터 패턴을 사용하면, 객체들이 직접적으로 서로를 참조하지 않고, 미디에이터를 통해 간접적으로 상호작용할 수 있습니다.

// 미디에이터 인터페이스
public interface IMediator
{
    void Notify(object sender, string message);
}
// 구체적인 미디에이터
public class LibraryMediator : IMediator
{
    private Catalog _catalog;
    private MemberManagement _memberManagement;
    private Notification _notification;
    public LibraryMediator(Catalog catalog, MemberManagement memberManagement, Notification notification)
    {
        _catalog = catalog;
        _catalog.SetMediator(this);
        _memberManagement = memberManagement;
        _memberManagement.SetMediator(this);
        _notification = notification;
    }
    public void Notify(object sender, string message)
    {
        if (sender is Catalog && message.Contains("added"))
        {
            _notification.SendNotification(message);
        }
        else if (sender is MemberManagement && message.Contains("registered"))
        {
            _notification.SendNotification(message);
        }
    }
}
// 동료 객체 1: 카탈로그 관리
public class Catalog
{
    private IMediator _mediator;
    public void SetMediator(IMediator mediator) => _mediator = mediator;
    public void AddBook(string book)
    {
        Console.WriteLine($"Book '{book}' added to catalog.");
        _mediator.Notify(this, $"New book '{book}' added.");
    }
}
// 동료 객체 2: 회원 관리
public class MemberManagement
{
    private IMediator _mediator;
    public void SetMediator(IMediator mediator) => _mediator = mediator;
    public void RegisterMember(string member)
    {
        Console.WriteLine($"Member '{member}' registered.");
        _mediator.Notify(this, $"Welcome, {member}!");
    }
}
// 동료 객체 3: 알림 관리
public class Notification
{
    public void SendNotification(string message)
    {
        Console.WriteLine($"Notification: {message}");
    }
}
// 클라이언트 코드
public class Program
{
    public static void Main(string[] args)
    {
        Notification notification = new Notification();
        Catalog catalog = new Catalog();
        MemberManagement memberManagement = new MemberManagement();
        LibraryMediator mediator = new LibraryMediator(catalog, memberManagement, notification);
        // 책 추가 및 회원 등록
        catalog.AddBook("Design Patterns");
        memberManagement.RegisterMember("John Doe");
    }
}

IMediator 인터페이스

객체 간 상호작용을 조정하는 메서드를 정의합니다.

LibraryMediator 클래스

미디에이터 인터페이스를 구현하며, 객체 간의 상호작용을 관리합니다. 각 동료 객체에서 발생한 이벤트에 따라 적절한 행동을 수행합니다.

Catalog, MemberManagement 클래스

동료 객체로, 직접적으로 다른 객체와 상호작용하지 않고 미디에이터를 통해 간접적으로 상호작용합니다.

Mediator Pattern 장단점

장점

객체 간의 결합도 감소

객체들이 서로 직접 참조하지 않고, 미디에이터를 통해 상호작용하므로 결합도가 낮아집니다.

유연한 확장성

새로운 객체나 기능을 추가할 때, 기존 객체의 수정 없이 미디에이터에 새로운 상호작용을 추가하면 됩니다.

코드의 단순화

객체 간의 복잡한 상호작용을 미디에이터가 관리하므로, 객체들이 개별적으로 복잡한 로직을 가지지 않아도 됩니다.

단점

미디에이터 클래스의 복잡성 증가

모든 상호작용을 미디에이터에서 처리하게 되면, 미디에이터 클래스가 복잡해지고, 많은 책임을 가지게 될 수 있습니다.

해결 방안

상호작용 분리 및 모듈화

미디에이터 클래스가 너무 복잡해지지 않도록, 상호작용을 모듈화하거나 분리하여 처리할 수 있습니다. 상호작용의 종류에 따라 별도의 미디에이터 클래스를 도입하거나, 동작을 여러 클래스로 분리하여 처리할 수 있습니다.

public class BookMediator : IMediator
{
    public void Notify(object sender, string message)
    {
        // 책 관련 상호작용만 처리
    }
}
public class MemberMediator : IMediator
{
    public void Notify(object sender, string message)
    {
        // 회원 관련 상호작용만 처리
    }
}

이처럼 상호작용을 모듈화하면 미디에이터 클래스가 지나치게 복잡해지는 문제를 해결할 수 있습니다.

객체지향 원칙과의 관계

Mediator와 단일 책임 원칙

미디에이터는 객체 간 상호작용을 관리하는 하나의 책임만 가지므로 단일 책임 원칙을 잘 준수합니다.

Mediator와 개방_폐쇄 원칙

새로운 상호작용을 추가할 때, 기존 객체를 수정하지 않고 미디에이터를 확장할 수 있으므로 개방-폐쇄 원칙을 충족합니다.

Mediator와 의존 역전 원칙

객체들이 미디에이터 인터페이스에 의존하며, 구체적인 객체가 아닌 미디에이터를 통해 상호작용하므로 의존 역전 원칙을 따릅니다.

맺음말

미디에이터 패턴은 객체 간의 복잡한 상호작용을 중앙에서 관리함으로써 결합도를 줄이고, 유지보수성을 높이는 데 유용한 패턴입니다. 객체 간의 통신이 빈번하거나 복잡한 경우, 미디에이터 패턴을 통해 이러한 상호작용을 간단하고 효과적으로 관리할 수 있습니다.

심화학습

비동기 상호작용 처리

미디에이터 패턴을 비동기적으로 확장하면, 객체 간의 비동기 상호작용을 쉽게 관리할 수 있습니다. 예를 들어, 여러 동료 객체 간의 통신이 비동기적으로 이루어질 때, 미디에이터가 그 흐름을 관리하는 방식으로 구현할 수 있습니다.

public interface IAsyncMediator
{
    Task NotifyAsync(object sender, string message);
}
public class AsyncLibraryMediator : IAsyncMediator
{
    public async Task NotifyAsync(object sender, string message)
    {
        if (sender is Catalog)
        {
            await Task.Run(() => Console.WriteLine($"Async handling for {message}"));
        }
    }
}

객체 수가 많아질 때의 최적화

동료 객체가 많아지면, 미디에이터가 모든 객체 간의 상호작용을 처리하는데 부담이 될 수 있습니다. 이때, 객체 간의 상호작용 빈도에 따라 우선순위를 설정하거나, 캐싱을 활용해 불필요한 상호작용을 줄이는 방법을 사용할 수 있습니다.

public class PrioritizedMediator : IMediator
{
    public void Notify(object sender, string message)
    {
        if (sender is Catalog)
        {
            // 높은 우선순위 처리
        }
        else
        {
            // 낮은 우선순위 처리
        }
    }
}

이벤트 기반 상호작용

미디에이터 패턴을 이벤트 기반 구조로 변경하면, 동료 객체 간의 상호작용이 발생할 때 이벤트를 트리거하고, 미디에이터가 이를 처리하는 구조로 확장할 수 있습니다. 이를 통해 미디에이터의 로직을 더욱 효율적으로 관리할 수 있습니다.

public class EventMediator
{
    public event Action<string> OnBookAdded;
    public void AddBook(string book)
    {
        OnBookAdded?.Invoke(book);
    }
}

이 구조를 사용하면 미디에이터 패턴을 이벤트 중심으로 관리하여 객체 간 상호작용을 더 유연하게 처리할 수 있습니다.