Посередник (шаблон проєктування)
Посередник (англ. Mediator) — шаблон проєктування, відноситься до класу шаблонів поведінки.
Призначення
Визначає об'єкт, що інкапсулює спосіб взаємодії множини об'єктів. Посередник забезпечує слабку зв'язаність системи, звільняючи об'єкти від необхідності явно посилатися один на одного, і дозволяючи тим самим незалежно змінювати взаємодії між ними.
Застосовність
Слід використовувати шаблон Посередник у випадках, коли:
- існують об'єкти, зв'язки між котрими досить складні та чітко задані. Отримані при цьому залежності не структуровані та важкі для розуміння;
- не можна повторно використовувати об'єкт, оскільки він обмінюється інформацією з багатьма іншими об'єктами;
- поведінка, розподілена між кількома класами, повинна піддаватися налагодженню без створювання множини підкласів.
Структура
- Mediator — посередник:
- визначає інтерфейс для обміну інформацією з об'єктами Colleague;
- ConcreteMediator — конкретний посередник:
- реалізує кооперативну поведінку, координуючи дії об'єктів Colleague;
- володіє інформацією про колег, та підраховує їх;
- Класи Colleague — колеги:
- кожному класу Colleague відомо про свій об'єкт Mediator;
- усі колеги обмінюються інформацією виключно через посередника, інакше за його відсутності їм довелося б спілкуватися між собою напряму.
Відносини
Колеги посилають запити посередникові та отримують запити від нього. Посередник реалізує кооперативну поведінку шляхом переадресації кожного запиту відповідному колезі (або декільком з них).
Переваги та недоліки
Переваги
- Медіатор вказує логіку посередництва між колегами. З цієї причини це легше зрозуміти цю логіку, оскільки вона зберігається лише в одному класі.
- Класи колег повністю відокремлені. Додавання нового класу колеги дуже легко через цей рівень роз'єднання.
- Об'єкти колеги повинні спілкуватися лише з об'єктами посередника. Практично модель медіатора зменшує необхідні канали зв'язку (протоколи) від багатьох до багатьох до одного до багатьох і багатьох до одного.
- Оскільки вся логіка зв'язку вказується класом посередника, коли ця логіка потребує розширення, лише клас посередника потрібно розширити.
Недоліки
- Складність — на практиці посередники стають все більш складними та складними
Зв'язок із іншими патернами
- Посередник створює двосторонній зв'язок, часто незмінний. Забирає залежності між компонентами системи. Компоненти стають залежними від посередника. Спостерігач створює односторонній зв'язок, який може мінятись під час виконання програми. Таким чином одні об'єкти залежать від інших.
Приклади
C++
#include <iostream>
#include <string>
using namespace std;
class Colleague;
class Mediator;
class ConcreteMediator;
class ConcreteColleague1;
class ConcreteColleague2;
struct Mediator
{
virtual void Send(string const& message, Colleague *colleague) const = 0;
};
class Colleague
{
protected:
Mediator* mediator;
public:
explicit Colleague(Mediator *mediator) :mediator(mediator) {}
};
struct ConcreteColleague1 :public Colleague
{
explicit ConcreteColleague1(Mediator* mediator) :Colleague(mediator) {}
void Send(string const& message)
{
mediator->Send(message, this);
}
void Notify(string const& message)
{
cout << "Colleague1 gets message '" << message << "'" << endl;
}
};
struct ConcreteColleague2 :public Colleague
{
explicit ConcreteColleague2(Mediator *mediator) :Colleague(mediator) {}
void Send(string const& message)
{
mediator->Send(message, this);
}
void Notify(string const& message)
{
cout << "Colleague2 gets message '" << message << "'" << endl;
}
};
class ConcreteMediator :public Mediator
{
protected:
ConcreteColleague1 * m_Colleague1;
ConcreteColleague2 * m_Colleague2;
public:
void SetColleague1(ConcreteColleague1 *c)
{
m_Colleague1 = c;
}
void SetColleague2(ConcreteColleague2 *c)
{
m_Colleague2 = c;
}
virtual void Send(string const& message, Colleague *colleague) const
{
if (colleague == m_Colleague1)
{
m_Colleague2->Notify(message);
}
else if (colleague == m_Colleague2)
{
m_Colleague1->Notify(message);
}
}
};
void main()
{
ConcreteMediator m;
ConcreteColleague1 c1(&m);
ConcreteColleague2 c2(&m);
m.SetColleague1(&c1);
m.SetColleague2(&c2);
c1.Send("How are you?");
c2.Send("Fine, thanks");
}
C#
using System;
using System.Linq;
using System.Collections.Generic;
namespace MediatorPattern
{
public interface IMediator
{
void AddParticipant(IParticipant participant);
void BroadcastMessage(string message, IParticipant sender);
}
public class ConcreteMediator : IMediator
{
private readonly ICollection<IParticipant> _participants = new List<IParticipant>();
public void AddParticipant(IParticipant participant)
{
_participants.Add(participant);
}
public void BroadcastMessage(string message, IParticipant sender)
{
foreach (var participant in _participants.Where(p => p != sender))
{
participant.HandleMessage(message);
}
}
}
public interface IParticipant
{
void SendMessage(string message);
void HandleMessage(string message);
}
public class ConcreteParticipant : IParticipant
{
private readonly int _id;
protected readonly IMediator _mediator;
public ConcreteParticipant(int id, IMediator mediator)
{
_id = id;
_mediator = mediator;
}
public void SendMessage(string message)
{
Console.WriteLine($"Participant {_id} send message: {message}");
_mediator.BroadcastMessage(message, this);
}
public void HandleMessage(string message)
{
Console.WriteLine($"Participant {_id} recieve message: {message}");
}
}
class Program
{
static void Main(string[] args)
{
IMediator mediator = new ConcreteMediator();
IParticipant participant1 = new ConcreteParticipant(1, mediator);
IParticipant participant2 = new ConcreteParticipant(2, mediator);
IParticipant participant3 = new ConcreteParticipant(3, mediator);
mediator.AddParticipant(participant1);
mediator.AddParticipant(participant2);
mediator.AddParticipant(participant3);
participant1.SendMessage("Hello from first participant");
participant2.SendMessage("Hello from second participant");
}
}
}
using System;
using System.Collections.Generic;
namespace MediatorPattern
{
public interface IMediator<TEvent> where TEvent : EventArgs
{
void AddHandler(IParticipantHandler<TEvent> participant);
void BroadcastMessage(TEvent eventArgs);
}
public class UserCreatedEventArgs : EventArgs
{
public string User { get; set; }
}
public class UserMediator : IMediator<UserCreatedEventArgs>
{
private readonly ICollection<IParticipantHandler<UserCreatedEventArgs>> _participants = new List<IParticipantHandler<UserCreatedEventArgs>>();
public void AddHandler(IParticipantHandler<UserCreatedEventArgs> participant)
{
_participants.Add(participant);
}
public void BroadcastMessage(UserCreatedEventArgs eventArgs)
{
foreach (var participant in _participants)
{
participant.HandleEvent(eventArgs);
}
}
}
public class ReportUpdatedEventArgs : EventArgs
{
}
public class ReportMediator : IMediator<ReportUpdatedEventArgs>
{
private readonly ICollection<IParticipantHandler<ReportUpdatedEventArgs>> _participants = new List<IParticipantHandler<ReportUpdatedEventArgs>>();
public void AddHandler(IParticipantHandler<ReportUpdatedEventArgs> participant)
{
_participants.Add(participant);
}
public void BroadcastMessage(ReportUpdatedEventArgs eventArgs)
{
foreach (var participant in _participants)
{
participant.HandleEvent(eventArgs);
}
}
}
public interface IParticipantHandler<T> where T : EventArgs
{
void HandleEvent(T message);
}
public interface IParticipantProducer<T> where T : EventArgs
{
void SendMessage(T message);
}
public class DbParticipant : IParticipantProducer<UserCreatedEventArgs>
{
protected readonly IMediator<UserCreatedEventArgs> _mediator;
public DbParticipant(IMediator<UserCreatedEventArgs> mediator)
{
_mediator = mediator;
}
public void SendMessage(UserCreatedEventArgs message)
{
_mediator.BroadcastMessage(message);
}
public void AddUser(string newUser)
{
SendMessage(new UserCreatedEventArgs
{
User = newUser
});
}
}
public class ReportBuilderParticipant : IParticipantProducer<ReportUpdatedEventArgs>, IParticipantHandler<UserCreatedEventArgs>
{
protected readonly IMediator<ReportUpdatedEventArgs> _mediator;
public ReportBuilderParticipant(IMediator<ReportUpdatedEventArgs> mediator)
{
_mediator = mediator;
}
public void HandleEvent(UserCreatedEventArgs message)
{
UpdateReport(message);
}
private void UpdateReport(UserCreatedEventArgs message)
{
Console.WriteLine($"[Report] Add to report new user: {message.User}");
SendMessage(new ReportUpdatedEventArgs());
}
public void SendMessage(ReportUpdatedEventArgs message)
{
_mediator.BroadcastMessage(message);
}
}
public class NotificationParticipant : IParticipantHandler<ReportUpdatedEventArgs>, IParticipantHandler<UserCreatedEventArgs>
{
protected readonly IMediator<ReportUpdatedEventArgs> _mediator;
public NotificationParticipant(IMediator<ReportUpdatedEventArgs> mediator)
{
_mediator = mediator;
}
public void HandleEvent(ReportUpdatedEventArgs message)
{
Console.WriteLine("[Notification] Report is updated and ready to download");
}
public void HandleEvent(UserCreatedEventArgs message)
{
Console.WriteLine($"[Notification] User {message.User} joined chat");
}
}
class Program
{
static void Main(string[] args)
{
IMediator<UserCreatedEventArgs> userMediator = new UserMediator();
IMediator<ReportUpdatedEventArgs> reportMediator = new ReportMediator();
var db = new DbParticipant(userMediator);
var reportBuilder = new ReportBuilderParticipant(reportMediator);
var notificationSystem = new NotificationParticipant(reportMediator);
userMediator.AddHandler(reportBuilder);
userMediator.AddHandler(notificationSystem);
reportMediator.AddHandler(notificationSystem);
db.AddUser("John Doe");
}
}
}
Java
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
//Colleague interface
interface Command {
void execute();
}
//Abstract Mediator
interface Mediator {
void book();
void view();
void search();
void registerView(BtnView v);
void registerSearch(BtnSearch s);
void registerBook(BtnBook b);
void registerDisplay(LblDisplay d);
}
//Concrete mediator
class ParticipantMediator implements Mediator {
BtnView btnView;
BtnSearch btnSearch;
BtnBook btnBook;
LblDisplay show;
//....
public void registerView(BtnView v) {
btnView = v;
}
public void registerSearch(BtnSearch s) {
btnSearch = s;
}
public void registerBook(BtnBook b) {
btnBook = b;
}
public void registerDisplay(LblDisplay d) {
show = d;
}
public void book() {
btnBook.setEnabled(false);
btnView.setEnabled(true);
btnSearch.setEnabled(true);
show.setText("booking...");
}
public void view() {
btnView.setEnabled(false);
btnSearch.setEnabled(true);
btnBook.setEnabled(true);
show.setText("viewing...");
}
public void search() {
btnSearch.setEnabled(false);
btnView.setEnabled(true);
btnBook.setEnabled(true);
show.setText("searching...");
}
}
//A concrete colleague
class BtnView extends JButton implements Command {
Mediator med;
BtnView(ActionListener al, Mediator m) {
super("View");
addActionListener(al);
med = m;
med.registerView(this);
}
public void execute() {
med.view();
}
}
//A concrete colleague
class BtnSearch extends JButton implements Command {
Mediator med;
BtnSearch(ActionListener al, Mediator m) {
super("Search");
addActionListener(al);
med = m;
med.registerSearch(this);
}
public void execute() {
med.search();
}
}
//A concrete colleague
class BtnBook extends JButton implements Command {
Mediator med;
BtnBook(ActionListener al, Mediator m) {
super("Book");
addActionListener(al);
med = m;
med.registerBook(this);
}
public void execute() {
med.book();
}
}
class LblDisplay extends JLabel {
Mediator med;
LblDisplay(Mediator m) {
super("Just start...");
med = m;
med.registerDisplay(this);
setFont(new Font("Arial", Font.BOLD, 24));
}
}
class MediatorDemo extends JFrame implements ActionListener {
Mediator med = new ParticipantMediator();
MediatorDemo() {
JPanel p = new JPanel();
p.add(new BtnView(this, med));
p.add(new BtnBook(this, med));
p.add(new BtnSearch(this, med));
getContentPane().add(new LblDisplay(med), "North");
getContentPane().add(p, "South");
setSize(400, 200);
setVisible(true);
}
public void actionPerformed(ActionEvent ae) {
Command comd = (Command) ae.getSource();
comd.execute();
}
public static void main(String[] args) {
new MediatorDemo();
}
}
Python
"""
Типовий приклад посередника зустрічається у фреймворку(програмному каркасі)
автоматизованого тестування який складається з чотиртох класів:
TC (TestCategory),
TestManager,
Reporter
DB(Database)
"""
import time
class TC:
def __init__(self):
self._tm = tm
self._bProblem = 0
def setup(self):
print("Setting up the Test")
time.sleep(1)
self._tm.prepareReporting()
def execute(self):
if not self._bProblem:
print("Executing the test")
time.sleep(1)
else:
print("Problem in setup. Test not executed.")
def tearDown(self):
if not self._bProblem:
print("Tearing down")
time.sleep(1)
self._tm.publishReport()
else:
print("Test not executed. No tear down required.")
def setTM(self,TM):
self._tm = tm
def setProblem(self, value):
self._bProblem = value
class Reporter:
def __init__(self):
self._tm = None
def prepare(self):
print("Reporter Class is preparing to report the results")
time.sleep(1)
def report(self):
print("Reporting the results of Test")
time.sleep(1)
def setTM(self,TM):
self._tm = tm
class DB:
def __init__(self):
self._tm = None
def insert(self):
print("Inserting the execution begin status in the Database")
time.sleep(1)
#Following code is to simulate a communication from DB to TC
import random
if random.randrange(1,4) == 3:
return -1
def update(self):
print("Updating the test results in Database")
time.sleep(1)
def setTM(self,TM):
self._tm = tm
class TestManager:
def __init__(self):
self._reporter = None
self._db = None
self._tc = None
def prepareReporting(self):
rvalue = self._db.insert()
if rvalue == -1:
self._tc.setProblem(1)
self._reporter.prepare()
def setReporter(self, reporter):
self._reporter = reporter
def setDB(self, db):
self._db = db
def publishReport(self):
self._db.update()
rvalue = self._reporter.report()
def setTC(self,tc):
self._tc = tc
if __name__ == '__main__':
reporter = Reporter()
db = DB()
tm = TestManager()
tm.setReporter(reporter)
tm.setDB(db)
reporter.setTM(tm)
db.setTM(tm)
# For simplification we are looping on the same test.
# Practically, it could be about various unique test classes and their objects
while (1):
tc = TC()
tc.setTM(tm)
tm.setTC(tc)
tc.setup()
tc.execute()
tc.tearDown()
TypeScript
@Injectable()
class EventMediator
{
private customerChangedSubject$ = new BehaviorSubject<CustomerData>(null);
public customerChanged = this.customerChangedSubject$.asObservable();
public notifyOnCustomerChanged(customerData: CustomerData): void {
this.customerChangedSubject$.next(customerData);
}
private productChangedSubject$ = new BehaviorSubject<ProductData>(null);
public productChanged = this.productChangedSubject$.asObservable();
public notifyOnProductChanged(productData: ProductData): void {
this.productChangedSubject$.next(productData);
}
}
// надсилання подій
this.eventMediator.notifyOnCustomerChanged(new CustomerData());
// обробка подій
this.eventMediator.customerChanged.subscribe(c => this.customer = c);
enum Events {
CustomerSelected
}
class EmitEvent{
constructor(public name: Events, value?: any) { }
}
@Injectable()
class EventBus
{
private subject = new Subject<any>();
on(event: Events, action: any): Subscription {
return this.subject
.pipe(
filter((e: EmitEvent) => e.name === event),
map((e: EmitEvent) => e.value),
).subscribe(action);
}
emit(event: EmitEvent): void {
this.subject.next(event);
}
}
// надсилання подій
this.eventBus.emit(new EmitEvent(Events.CustomerSelected), new CustomerData());
// обробка подій
this.eventBus.on(Events.CustomerSelected, c => this.customer = c);
Література
Алан Шаллоуей, Джеймс Р. Тротт. Шаблоны проектирования. Новый подход к объектно-ориентированному анализу и проектированию = Design Patterns Explained: A New Perspective on Object-Oriented Design. — М. : «Вильямс», 2002. — 288 с. — ISBN 0-201-71594-5.