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.