Посредник (Mediator) — шаблон проектирования
1 февраля 2020
Шаблон предлагает схему, при которой объекты взаимодействуют между собой не напрямую и явные связи, а через объект-посредник. Это позволяет перенести логику взаимодействия множества объектов в централизованное место – в класс посредника.
Код реализации паттерна на языке PHP. Скачать исходники (ZIP, 3 Кб)
Рассмотрим классы подробнее.
Далее создаются участники чата – клиент и сотрудник техподдержики:
Затем происходит обмен сообщениями между клиентом и техподдержкой
Отмечу, что у класса пользователя User в методе приёма сообщений receiveMessage($message) реализован вывод на экран текста пришедшего сообщения, а так же имени отправителя и получателя.
В конце скрипта выводится история сообщений. Полный код скрипта test.php
Результат работы скрипта test.php
Как можно заметить, участники чата не отправляют сообщения друг-другу напрямую. Вместо этого они передают сообщения посреднику – объекту-чату класса ChatSupportMediator, который выполняет роль логистического центра, получая, обрабатывая и направляя сообщения целевым адресатам.
ПРИМЕР ЗАДАЧИ: сделать отправку и приём сообщений между пользователем-клиентом и службой техподдержки.
Код реализации паттерна на языке PHP. Скачать исходники (ZIP, 3 Кб)
chat_support_mediator.classes.php
Содержит классы:- ChatSupportMediator – чат между пользователем и техподдержкой;
- Message – сообщение участника чата;
- User – абстрактный класс пользователя;
- UserClient – пользователь-клиент, участник чата, наследник класса User;
- TechAgent – специалист техподдержки, участник чата, наследник класса User.
Рассмотрим классы подробнее.
ChatSupportMediator
Чат для общения между клиентами и сотрудниками техподдержки. Именно этот класс выступает в роли посредника, осуществляя обмен сообщений между участниками чата. Класс содержит следующие свойства и методы:- $users – массив ссылок на участников чата, представленные объектами класса User;
- $messagesHistory – массив с историей сообщений;
- addUser($user) – добавляет пользователя в чат;
- getMessagesHistory() – возвращает массив с историей сообщений чата;
- sendMessage($message) – отправляет сообщение $message участникам чата и затем помещает сообщения в историю переписки. Метод обходит свойство-массив $users, содержащее участников чата и передаёт сообщение $message, каждому пользователю (за исключением автора сообщения), вызывая у них метод receiveMessage($message).
/**
* Чат техподдержки (класс-посредник при обмене сообщений между пользователем и техподдержкой)
*/
class ChatSupportMediator
{
/**
* Массив участников чата
* @var User[]
*/
private $users = array();
/**
* Массив с историей сообщений
* @var Message[]
*/
private $messagesHistory = array();
/**
* Добавляет пользователя в участники чата
* @param User $user - добавляемый пользователь
*/
public function addUser($user)
{
$this->users[$user->getId()] = $user;
}
/**
* Возвращает массив с историей сообщений чата
* @return Message[]
*/
public function getMessagesHistory()
{
return $this->messagesHistory;
}
/**
* Отправляет сообщение участникам чата
* @param Message $message - сообщение
*/
public function sendMessage($message)
{
//Передаём сообщение участникам чата
foreach ($this->users as $user) {
if ($user->getId() != $message->getAuthorId()) {
$user->receiveMessage($message);
}
}
//Добавляем сообщение в историю чата
$this->messagesHistory[] = $message;
}
}
Message
Представляет класс сообщения. Состоит из следующих свойств и методов:- $author – автор сообщения (объект класса User);
- $text – текст сообщения;
- $dateCreate – дата создания (указывается в виде метки времени);
- Конструктор __construct($author, $text) – конструктор, создаёт объект сообщения;
- getAuthorId() – возвращает ID автора сообщения;
- getText() – возвращает текст сообщения;
- getDateCreate() – возвращает дату создания сообщения;
- getAuthorTitle() – возвращает заголовок автора. Используется, чтобы выводить не только имя автора сообщения, но ещё при необходимости добавлять другую информацию. Например, для сотрудников техподдержки в вывод добавляется их должность.
/**
* Сообщение
*/
class Message
{
/**
* Автор сообщения
* @var User
*/
private $author;
/**
* Текст сообщения
* @var string
*/
private $text;
/**
* Дата создания (указывается в виде метки времени)
* @var int
*/
private $dateCreate;
/**
* Конструктор. Создаёт объект сообщения
* @param User $author - автор сообщения
* @param string $text - текст сообщения
*/
public function __construct($author, $text)
{
$this->author = $author;
$this->text = $text;
$this->dateCreate = time();
}
/**
* Возвращает ID автора сообщения
* @return int
*/
public function getAuthorId()
{
return $this->author->getId();
}
/**
* Возвращает текст сообщения
* @return string
*/
public function getText()
{
return $this->text;
}
/**
* Возвращает дату создания сообщения
* @return int
*/
public function getDateCreate()
{
return $this->dateCreate;
}
/**
* Возвращает заголовок автора
* @return string
*/
public function getAuthorTitle()
{
return $this->author->getTitle();
}
}
User
Представляет абстрактный класс пользователя чата. Состоит из следующих свойств и методов:- $chat – чат, в котором принимает участие пользователь. Содержит ссылку на объект класса ChatSupportMediator;
- $id – ID пользователя;
- $name – имя пользователя;
- Конструктор __construct($chat, $id, $name) – конструктор, создаёт объект пользователя (в аргументах указывается чат, ID пользователя и его имя);
- getId() – возвращает ID пользователя;
- getName() – возвращает имя пользователя;
- getTitle() – возвращает заголовок пользователя. Используется, чтобы выводить не только имя автора сообщения, но ещё при необходимости добавлять другую информацию. Например, для сотрудников техподдержки в вывод добавляется их должность.
- sendMessage($text) – выполняет отправку сообщения в чат. Внутри себя метод передаёт сообщение в чат с помощью вызова одноимённого метода sendMessage($message) у свойства $chat (объект класса ChatSupportMediator), который затем пересылает сообщение всем остальным участникам чата;
- receiveMessage($message) – выполняет приём сообщения из чата. В нашем тестовом примере текст присланного сообщения выводится на экран.
/**
* Абстрактный класс пользователя чата
*/
abstract class User
{
/**
* Чат, в котором участвует пользователь
*/
private $chat;
/**
* ID пользователя
* @var int
*/
protected $id;
/**
* Имя
* @var string
*/
protected $name;
/**
* Конструктор. Создаёт объект пользователя
* @param ChatSupportMediator $chat - чат, в котором участвует пользователь
* @param int $id - ID пользователя
* @param string $name - имя
*/
public function __construct($chat, $id, $name)
{
$this->id = $id;
$this->name = $name;
//Добавляем пользователя в чат
$this->chat = $chat;
$this->chat->addUser($this);
}
/**
* Возвращает ID пользователя
* @return int
*/
public function getId()
{
return $this->id;
}
/**
* Возвращает имя пользователя
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Возвращает заголовок пользователя
* @return string
*/
public function getTitle()
{
return $this->name;
}
/**
* Выполняет отправку сообщения в чат
*/
public function sendMessage($text)
{
$message = new Message($this, $text);
$this->chat->sendMessage($message);
}
/**
* Приём сообщения из чата
* @param Message $message - сообщение
*/
public function receiveMessage($message)
{
echo '
КОМУ: '.$this->getTitle().'<br>
ОТ: '.$message->getAuthorTitle().'<br>
'.$message->getText().'<br><br>';
//Тут размещается код, который выводит сообщение в чат пользователя
}
}
UserClient
Пользователь-клиент, наследник класса User. Своих новых свойств и методов класс не имеет, использует свойства и методы родительского класса User./**
* Пользователь-клиент
*/
class UserClient extends User
{}
TechAgent
Сотрудник техподдержки, наследник класса User. Содержит следующие свойства и методы:- $position – должность сотрудника;
- Конструктор __construct($chat, $id, $name, $position) – конструктор, создаёт объект сотрудника (в аргументах указывается чат, ID пользователя, его имя и должность);
- getTitle() – возвращает заголовок пользователя. В отличие от родительского метода, данный метод в результат добавляет ещё должность сотрудника.
/**
* Специалист техподдержки
*/
class TechAgent extends User
{
/**
* Должность сотрудника
* @var string
*/
private $position;
/**
* Конструктор. Создаёт объект специалиста
* @param ChatSupportMediator $chat - чат, в котором участвует пользователь
* @param int $id - ID сотрудника
* @param string $name - имя
* @param string $position - должность сотрудника
*/
public function __construct($chat, $id, $name, $position)
{
//Вызываем конструктор родителя
parent::__construct($chat, $id, $name);
$this->position = $position;
}
/**
* Возвращает заголовок пользователя
* @return string
*/
public function getTitle()
{
return $this->name.' ('.($this->position).')';
}
}
test.php
Содержит тестовый скрипт, в котором сначала создаётся чат $chat (объект класса ChatSupportMediator)://Создаём чат
$chat = new ChatSupportMediator();
Далее создаются участники чата – клиент и сотрудник техподдержики:
//Создаём пользователя-клиента
$userId = 'u1';
$userClient = new UserClient($chat, $userId, 'Ольга');
//Создаём специалиста техподдержки
$userId = 't1';
$techAgent_1 = new TechAgent($chat, $userId, 'Александр Зуев', 'Бухгалтерская служба');
Затем происходит обмен сообщениями между клиентом и техподдержкой
//Переписка между лиентом и специалистом техподдержки
$userClient->sendMessage('Здравствуйте. Почему повысился тариф?');
$techAgent_1->sendMessage('Здравствуйте. Сейчас посмотрю, 2-3 минуты');
$techAgent_1->sendMessage('Посмотрел. Прошлая стоимость тарифа сохранялась на протяжении почти трёх лет, но из-за инфляции услуга для нас стала убыточной');
$techAgent_1->sendMessage('Компания была вынуждена рано или поздно повысить цены');
$techAgent_1->sendMessage('За месяц до повышения мы оповестили всех клиентов о грядущем увеличении стоимости на 15%');
$userClient->sendMessage('Ясно. Скажите, а можно вызвать мастера на дом?');
$techAgent_1->sendMessage('Думаю, что да. Подождите минутку, позову в чат профильного специалиста');
Отмечу, что у класса пользователя User в методе приёма сообщений receiveMessage($message) реализован вывод на экран текста пришедшего сообщения, а так же имени отправителя и получателя.
В конце скрипта выводится история сообщений. Полный код скрипта test.php
<?
//Подключаем классы для работы с чатом техподдержки
require_once(__DIR__.'/chat_support_mediator.classes.php');
//Создаём чат
$chat = new ChatSupportMediator();
//Создаём пользователя-клиента
$userId = 'u1';
$userClient = new UserClient($chat, $userId, 'Ольга');
//Создаём специалиста техподдержки
$userId = 't1';
$techAgent_1 = new TechAgent($chat, $userId, 'Александр Зуев', 'Бухгалтерская служба');
//Переписка между лиентом и специалистом техподдержки
$userClient->sendMessage('Здравствуйте. Почему повысился тариф?');
$techAgent_1->sendMessage('Здравствуйте. Сейчас посмотрю, 2-3 минуты');
$techAgent_1->sendMessage('Посмотрел. Прошлая стоимость тарифа сохранялась на протяжении почти трёх лет, но из-за инфляции услуга для нас стала убыточной');
$techAgent_1->sendMessage('Компания была вынуждена рано или поздно повысить цены');
$techAgent_1->sendMessage('За месяц до повышения мы оповестили всех клиентов о грядущем увеличении стоимости на 15%');
$userClient->sendMessage('Ясно. Скажите, а можно вызвать мастера на дом?');
$techAgent_1->sendMessage('Думаю, что да. Подождите минутку, позову в чат профильного специалиста');
//Создаём второго специалиста техподдержки
$userId = 't2';
$techAgent_2 = new TechAgent($chat, $userId, 'Илья Туманов', 'Консультант розничных клиентов');
//Продолжаем переписку
$techAgent_2->sendMessage('Ольга, здравствуйте. В какой день и во сколько вам будет удобно принять нашего мастера?');
$userClient->sendMessage('Удобно на 5 февраля, в 10-11 часов утра');
$techAgent_2->sendMessage('Отлично. Заявку на выезд мастера создал');
$userClient->sendMessage('Спасибо');
//Выводим историю переписки чата
echo '<br>ИСТОРИЯ ПЕРЕПИСКИ:<br><br>';
$num = 0;
$messageHistory = $chat->getMessagesHistory();
foreach ($messageHistory as $message) {
$num++;
echo $num.') '.$message->getAuthorTitle().'<br>';
echo $message->getText().'<br>';
echo '--------------------<br>';
}
?>
Результат работы скрипта test.php
КОМУ: Александр Зуев (Бухгалтерская служба)
ОТ: Ольга
Здравствуйте. Почему повысился тариф?
КОМУ: Ольга
ОТ: Александр Зуев (Бухгалтерская служба)
Здравствуйте. Сейчас посмотрю, 2-3 минуты
КОМУ: Ольга
ОТ: Александр Зуев (Бухгалтерская служба)
Посмотрел. Прошлая стоимость тарифа сохранялась на протяжении почти трёх лет, но из-за инфляции услуга для нас стала убыточной
КОМУ: Ольга
ОТ: Александр Зуев (Бухгалтерская служба)
Компания была вынуждена рано или поздно повысить цены
КОМУ: Ольга
ОТ: Александр Зуев (Бухгалтерская служба)
За месяц до повышения мы оповестили всех клиентов о грядущем увеличении стоимости на 15%
КОМУ: Александр Зуев (Бухгалтерская служба)
ОТ: Ольга
Ясно. Скажите, а можно вызвать мастера на дом?
КОМУ: Ольга
ОТ: Александр Зуев (Бухгалтерская служба)
Думаю, что да. Подождите минутку, позову в чат профильного специалиста
КОМУ: Ольга
ОТ: Илья Туманов (Консультант розничных клиентов)
Ольга, здравствуйте. В какой день и во сколько вам будет удобно принять нашего мастера?
КОМУ: Александр Зуев (Бухгалтерская служба)
ОТ: Илья Туманов (Консультант розничных клиентов)
Ольга, здравствуйте. В какой день и во сколько вам будет удобно принять нашего мастера?
КОМУ: Александр Зуев (Бухгалтерская служба)
ОТ: Ольга
Удобно на 5 февраля, в 10-11 часов утра
КОМУ: Илья Туманов (Консультант розничных клиентов)
ОТ: Ольга
Удобно на 5 февраля, в 10-11 часов утра
КОМУ: Ольга
ОТ: Илья Туманов (Консультант розничных клиентов)
Отлично. Заявку на выезд мастера создал
КОМУ: Александр Зуев (Бухгалтерская служба)
ОТ: Илья Туманов (Консультант розничных клиентов)
Отлично. Заявку на выезд мастера создал
КОМУ: Александр Зуев (Бухгалтерская служба)
ОТ: Ольга
Спасибо
КОМУ: Илья Туманов (Консультант розничных клиентов)
ОТ: Ольга
Спасибо
ИСТОРИЯ ПЕРЕПИСКИ:
1) Ольга
Здравствуйте. Почему повысился тариф?
--------------------
2) Александр Зуев (Бухгалтерская служба)
Здравствуйте. Сейчас посмотрю, 2-3 минуты
--------------------
3) Александр Зуев (Бухгалтерская служба)
Посмотрел. Прошлая стоимость тарифа сохранялась на протяжении почти трёх лет, но из-за инфляции услуга для нас стала убыточной
--------------------
4) Александр Зуев (Бухгалтерская служба)
Компания была вынуждена рано или поздно повысить цены
--------------------
5) Александр Зуев (Бухгалтерская служба)
За месяц до повышения мы оповестили всех клиентов о грядущем увеличении стоимости на 15%
--------------------
6) Ольга
Ясно. Скажите, а можно вызвать мастера на дом?
--------------------
7) Александр Зуев (Бухгалтерская служба)
Думаю, что да. Подождите минутку, позову в чат профильного специалиста
--------------------
8) Илья Туманов (Консультант розничных клиентов)
Ольга, здравствуйте. В какой день и во сколько вам будет удобно принять нашего мастера?
--------------------
9) Ольга
Удобно на 5 февраля, в 10-11 часов утра
--------------------
10) Илья Туманов (Консультант розничных клиентов)
Отлично. Заявку на выезд мастера создал
--------------------
11) Ольга
Спасибо
--------------------
Как можно заметить, участники чата не отправляют сообщения друг-другу напрямую. Вместо этого они передают сообщения посреднику – объекту-чату класса ChatSupportMediator, который выполняет роль логистического центра, получая, обрабатывая и направляя сообщения целевым адресатам.