A Mediator minta akkor jön jól, ha a rendszerünkben használt objektumok között túl sok a függőség. Kérdés az, hogy mi számít túl sok függőségnek? Erre egzaktul válaszolni nehéz, mivel szituáció függő, de képzeljünk el egy rendszert, amiben két komponensnek kommunikálnia kellene egymással. Egyszerű feladat, amit mondjuk eseményekkel megoldunk. Ezt követően jön egy harmadik komponens, majd egy negyedik, aminek direktben kommunikálnia kellene a már meglévő hárommal.
Ez nem egy ideális eset, mert ennek a naiv megvalósítása esetén a 4. komponensünknek tudnia kell közvetlenül a 3 már meglévőről, ami bekorlátozza a rendszer flexibilitását. Persze interfész szegregációval valamennyit lehet segíteni a dolgon, de azért ez sem ideális megoldás.
A mediator minta lényege, hogy a komponensek közötti direkt kommunikációt kiiktatjuk és egy központi, közvetítő (mediator) komponensen keresztül vezetjük el az adatokat. Ez segít abban, hogy elkerüljük a komponensek közötti túl sok direkt függőséget, mivel minden komponensnek csak egy függősége lesz: a központi mediator.
A mediator minta cloud és microservice alkalmazásokban igen népszerű és millió egy módon lehet implementálni.
Nézzünk egy egyszerű implementációt:
internal interface IMediator
{
void Notify(object sender, string message);
void RegisterNotifyable(INotifyable notifyable);
}
internal interface INotifyable
{
void HandleNotification(object sender, string message);
}
internal class Mediator : IMediator
{
private readonly List<INotifyable> _notifyables;
public Mediator()
{
_notifyables = new List<INotifyable>();
}
public void Notify(object sender, string message)
{
foreach (var notifyable in _notifyables)
{
notifyable.HandleNotification(sender, message);
}
}
public void RegisterNotifyable(INotifyable notifyable)
{
_notifyables.Add(notifyable);
}
}
internal class Component
{
protected IMediator _mediator;
public Component(IMediator mediator)
{
_mediator = mediator;
}
}
internal class Component1 : Component
{
public Component1(IMediator mediator) : base(mediator)
{
_mediator.Notify(this, "Component1 Created");
}
}
internal class PrinterComponent : Component, INotifyable
{
public PrinterComponent(IMediator mediator) : base(mediator)
{
_mediator.RegisterNotifyable(this);
}
public void HandleNotification(object sender, string message)
{
Console.WriteLine($"{sender}: {message}");
}
}
A kód két fontos eleme az IMediator és INotifyable interfész. Az IMediator interfészt a mediátort írja le. A komponensek ennek segítségével tudnak egymásnak üzenni, illetve ezzel tudják magukat beregisztrálni üzenet fogadásra. Az üzenet fogadáshoz a komponensnek implementálnia kell az INotifyable interfészt, aminek a HandleNotification metódusát fogja meghívni a mediátor üzenet továbbításhoz.
A példában Component1 példányosításakor azonnal küld egy üzenetet, amit a Mediator osztály továbbítani fog a PrinterComponent felé.
Ennek a megközelítésnek az előnye, hogy nincs függőség a Component1 és PrinterComponent között még interfészek formájában se.
A Mediator minta sokféleképpen implementálható. Az üzenetküldőt kicserélhetjük például egy TCP/IP elven működő JSON-t használó implementációra és már nem csak egy programon belül használható kapásból.
.NET programok esetén a MediatR csomag nyújt segítséget a Mediator minta implementálásában, elég flexibilis módon. A MediatR forráskódja a https://github.com/jbogard/MediatR címról szerezhető be.
Előnyök
- Követi a Single Responsibility elvet. Egy központi komponens feladata az üzenetküldés.
- Követi az Open/Closed elvet: A mediator implementáció cserélhető anélkül, hogy a komponenseket módosítanánk
- Csökkenthető vele az egymástól függő komponensek száma
- Egyszerűbben újrahasznosíthatóak a komponensek
Hátrányok
- Az idő előrehaladtával a Mediator implementáció God objektummá válhat.