НЕ МОЛЧИ!!!    Сделай что-нибудь, чтобы остановить войну России в Украине.
...бойтесь людей равнодушных - именно с их молчаливого согласия происходят все самые ужасные преступления на свете.   ("Репортаж с петлёй на шее")

Шаблон проектирования Mediator(Посредник)

Рассматривается поведенческий шаблон проектирования Mediator, условия его применения, пример реализации.

Содержание

  • Применение
  • Реализация
  • Итоги

Применение

Представьте, что вы разрабатываете многофункциональное приложение, усердно поработали и создали множество классов, взаимодействующих друг с другом. Но на каком-то этапе разработки может возникнуть примерно такая картина.

Архитектура приложения с кодом типа "лапша"
Компоненты взаимодействуют напрямую друг с другом

Что мы имеем? Компоненты тесно связаны, поскольку взаимодействуют напрямую друг с другом. Такие компоненты сложно изменять, тестировать, повторно использовать, т.к. они связаны с другими.
Если назвать полученное одним словом, то просится слово хаос. Такое приложение сложно поддерживать, развивать, в нем наверняка куча повторного кода и прочие прелести.
Избежать всего этого помогает шаблон Mediator, он же Посредник. Основная цель его использования — уменьшить связи между между компонентами приложения за счет перемещения взаимодействий в один отдельный компонент-посредник. В этом случае остальным компонентам приложения не надо знать друг о друге, достаточно будет взаимодействовать только с одним посредником, который все разрулит. Использование посредника уменьшается связанность элементов, что позволяет их многократно использовать. Также облегчается введение в приложение новых элементов, ведь теперь надо будет доработать в основном один класс посредника. Хорошим примером посредника являются диспетчерские службы, которые все взаимосвязи подразделений или исполнителей замыкают на себя, предоставляя последним заниматься своими непосредственными обязанностями.
С применением шаблона Mediator структура приведенного выше приложения станет такой.

Пример взаимодействия через шаблон Mediator
Компоненты взаимодействуют через Медиатор

Реализация

Для реализации шаблона нам понадобятся:

  • интерфейс IMediator — декларирует методы для взаимодействия компонентов приложения с посредником;
  • класс Mediator — обеспечивает взаимодействие конкретных компонентов на основе интерфейса IMediator;
  • абстрактный класс BaseComponent — определяет набор обязательных свойств и методов дочерних компонентов для связи с другими компонентами через Посредника;
  • класс Component — определяет бизнес-логику компонентов приложения, наследуется от абстрактного класса BaseComponent.

Итак, в нашем приложении будут использоваться различные компоненты, взаимодействующие друг с другом через строковые сообщения через объект посредника. Начнем с интерфейса посредника, в нашем случае достаточно декларировать методы, обеспечивающие управление сообщениями.

interface IMediator 
{
    public function processMessage(Component $component, string $message);
    
    public function getComponents($arr);
}

Класс конкретного посредника Mediator, кроме обработки сообщения, фиксирует время прихода сообщения и на основе входных параметров определяет получателя.

class Mediator implements IMediator
{
    private $components;

    public function getComponents($arr)
    {
        $this->components = $arr;
    }
    
    public function processMessage(Component $component, string $message)
    {
        $senderName = $component->getName();                      //имя отправителя
        $time = date('d.m.Y H:i');
        $recipient = $this->getRecipient($senderName, $message);  //определяем получателя
        if (isset($recipient)) {
            $recipient->getMessage($message, $time);
        } else {
            echo 'Посредник: компонент не найден';
        }
    }

    //определяет получателя сообщения 
    private function getRecipient(string $name, string $message = '')
    {
        $recipient;
        if (isset($this->components)) {
            switch ($this->components[$name]->getName()) {
                case 'component1':
                    $recipient = $this->components['component2'];
                    break;
                case 'component2':
                    $recipient = $this->components['component1'];;
                    break;
                default:
                    $recipient = null;
            }
        }      
        return $recipient;
    }
}

Родительский класс компонентов, определяет обязательные свойства и методы компонентов приложения для их взаимодействия.

abstract class BaseComponent
{
    private $name;
    private $mediator;    //ссылка на посредник

    abstract public function sendMessage(string $message);
    abstract public function getMessage(string $message);
    public function getName() {
        return $this->name;
    }
}

Определим конкретный компонент приложения.

class Component extends BaseComponent
{
    private $name;
    private $mediator;
    private $components; 

    public function __construct(string $name, Mediator $mediator)
    {
        $this->name = $name;
        $this->mediator = $mediator;
    }
    
    public function sendMessage($message)
    {
        $this->mediator->processMessage($this, $message);
    }

    public function getMessage($message)
    {
        //код обработки поступившего сообщения ...

        $time = date('d.m.Y H:i');
        echo '<b>' . $this->getName() . '</b> обработал сообщение ' 
                . '"' . $message . '"' . ' в ' . $time;
    }

    public function getName()
    {
        return $this->name;
    }
}

Вот как это будет работать в приложении.

$mediator = new Mediator();
$component1 = new Component('component1', $mediator);
$component2 = new Component('component2', $mediator);

$components = [];
$components[$component1->getName()] = $component1;
$components[$component2->getName()] = $component2;
$mediator->getComponents($components);

$message = 'Сообщение1';
echo  '<b>' . $component1->getName() . '</b> отправил сообщение "' . $message . '"<br>';

$component1->sendMessage($message);

В приложении создается посредник $mediator и компоненты программы $component1 и $component2. Обратите внимание, компоненты ничего не знают друг о друге. При возникновении какого-либо события в компоненте(мы имитировали сообщение $message), ему достаточно лишь отправить об этом сообщение, a его обработку в системе берет на себя посредник $mediator. Результат работы примера будет таким.

component1 отправил сообщение "Сообщение1"
component2 обработал сообщение "Сообщение1" в 14.04.2024 09:22

Итоги

Итак, было показано, что использование шаблона проектирования Mediator позволяет:

  • существенно упростить архитектуру программного обеспечения за счет ослабления связей между элементами программы;
  • сосредоточить управление взаимодействием элементов в одном месте;
  • упростить взаимодействие объектов;
  • добиться повторного использования кода без его модификации;
  • упростить доработку ПО при расширении функционала программы.