Паттерн Mediator (посредник)
Назначение паттерна Mediator
- Паттерн Mediator определяет объект, инкапсулирующий взаимодействие множества объектов. Mediator делает систему слабо связанной, избавляя объекты от необходимости ссылаться друг на друга, что позволяет изменять взаимодействие между ними независимо.
- Паттерн Mediator вводит посредника для развязывания множества взаимодействующих объектов.
- Заменяет взаимодействие "все со всеми" взаимодействием "один со всеми".
Решаемая проблема
Мы хотим спроектировать систему с повторно используемыми компонентами, однако существующие связи между этими компонентами можно охарактеризовать феноменом "спагетти-кода".
Спагетти-код - плохо спроектированная, слабо структурированная, запутанная и трудная для понимания программа. Спагетти-код назван так, потому что ход выполнения программы похож на миску спагетти, то есть извилистый и запутанный.
Обсуждение паттерна Mediator
В Unix права доступа к системным ресурсам определяются тремя уровнями: владелец, группа и прочие. Группа представляет собой совокупность пользователей, обладающих некоторой функциональной принадлежностью. Каждый пользователь в системе может быть членом одной или нескольких групп, и каждая группа может иметь 0 или более пользователей, назначенных этой группе. Следующий рисунок показывает трех пользователей, являющихся членами всех трех групп.
Если нам нужно было бы построить программную модель такой системы, то мы могли бы связать каждый объект User c каждым объектом Group, а каждый объект Group - с каждым объектом User. Однако из-за наличия множества взаимосвязей модифицировать поведение такой системы очень непросто, пришлось бы изменять все существующие классы.
Альтернативный подход - введение "дополнительного уровня косвенности" или построение абстракции из отображения (соответствия) пользователей в группы и групп в пользователей. Такой подход обладает следующими преимуществами: пользователи и группы отделены друг от друга, отображениями легко управлять одновременно и абстракция отображения может быть расширена в будущем путем определения производных классов.
Разбиение системы на множество объектов в общем случае повышает степень повторного использования, однако множество взаимосвязей между этими объектами, как правило, приводит к обратному эффекту. Чтобы этого не допустить, инкапсулируйте взаимодействия между объектами в объект-посредник. Действуя как центр связи, этот объект-посредник контролирует и координирует взаимодействие группы объектов. При этом объект-посредник делает взаимодействующие объекты слабо связанными, так как им больше не нужно хранить ссылки друг на друга – все взаимодействие идет через этого посредника. Расширить или изменить это взаимодействие можно через его подклассы.
Паттерн Mediator заменяет взаимодействие "все со всеми" взаимодействием "один со всеми".
Пример рационального использования паттерна Mediator – моделирование отношений между пользователями и группами операционной системы. Группа может иметь 0 или более пользователей, а пользователь может быть членом 0 или более групп. Паттерн Mediator предусматривает гибкий способ управления пользователями и группами.
Структура паттерна Mediator
UML-диаграмма классов паттерна Mediator
Коллеги (или взаимодействующие объекты) не связаны друг с другом. Каждый из них общается с посредником, который, в свою очередь, знает об остальных и управляет ими. Паттерн Mediator делает статус взаимодействия "все со всеми" "полностью объектным".
Пример паттерна Mediator
Паттерн Mediator определяет объект, управляющий набором взаимодействующих объектов. Слабая связанность достигается благодаря тому, что вместо непосредственного взаимодействия друг с другом коллеги общаются через объект-посредник. Башня управления полетами в аэропорту хорошо демонстрирует этот паттерн. Пилоты взлетающих или идущих на посадку самолетов в районе аэропорта общаются с башней вместо непосредственного общения друг с другом. Башня определяет, кто и в каком порядке будет садиться или взлетать. Важно отметить, что башня контролирует самолеты только в районе аэродрома, а не на протяжении всего полета.
Использование паттерна Mediator
- Определите совокупность взаимодействующих объектов, связанность между которыми нужно уменьшить.
- Инкапсулируйте все взаимодействия в абстракцию нового класса.
- Создайте экземпляр этого нового класса. Объекты-коллеги для взаимодействия друг с другом используют только этот объект.
- Найдите правильный баланс между принципом слабой связанности и принципом распределения ответственности.
- Будьте внимательны и не создавайте объект-"контроллер" вместо объекта-посредника.
Особенности паттерна Mediator
- Паттерны Chain of Responsibility, Command, Mediator и Observer показывают, как можно разделить отправителей и получателей запросов с учетом их особенностей. Chain of Responsibility передает запрос отправителя по цепочке потенциальных получателей. Command номинально определяет связь - "оправитель-получатель" с помощью подкласса. В Mediator отправитель и получатель ссылаются друг на друга косвенно, через объект-посредник. В паттерне Observer связь между отправителем и получателем слабее, при этом число получателей может конфигурироваться во время выполнения.
- Mediator и Observer являются конкурирующими паттернами. Если Observer распределяет взаимодействие c помощью объектов "наблюдатель" и "субъект", то Mediator использует объект-посредник для инкапсуляции взаимодействия между другими объектами. Мы обнаружили, что легче сделать повторно используемыми Наблюдателей и Субъектов, чем Посредников.
- С другой стороны, Mediator может использовать Observer для динамической регистрации коллег и их взаимодействия с посредником.
- Mediator похож Faсade в том, что он абстрагирует функциональность существующих классов. Mediator абстрагирует/централизует взаимодействие между объектами-коллегами, добавляет новую функциональность и известен всем объектам-коллегам (то есть определяет двунаправленный протокол взаимодействия). Facade, наоборот, определяет более простой интерфейс к подсистеме, не добавляя новой функциональности, и неизвестен классам подсистемы (то есть имеет однонаправленный протокол взаимодействия, то есть запросы отправляются в подсистему, но не наоборот).
Реализация паттерна Mediator
Демонстрация паттерна Mediator
Степень повторного использования можно повысить через разбиение системы на множество объектов, однако при этом возникает множество взаимосвязей между этими объектами, которое приводит к обратному эффекту. Для исключения этой проблемы инкапсулируйте взаимодействие между объектами (коллективное поведение) в объект-посредник. Этот посредник будет управлять взаимодействием группы объектов.
В этом примере объект диалогового окна функционирует в качестве посредника. Виджеты диалогового окна ничего не знают о своих соседях. Всякий раз, когда происходит взаимодействие с пользователем виджет в Widget::changed()
"делегирует" это событие посреднику mediator->widgetChanged(this)
.
FileSelectionDialog:: widgetChanged()
инкапсулирует все коллективное поведение для диалогового окна (служит центром взаимодействия). Пользователь может выбрать "взаимодействие" с полем редактирования Filter, списком Directories, списком Files или полем редактирования Selection.
#include <iostream.h>
class FileSelectionDialog;
class Widget
{
public:
Widget(FileSelectionDialog *mediator, char *name)
{
_mediator = mediator;
strcpy(_name, name);
}
virtual void changed();
virtual void updateWidget() = 0;
virtual void queryWidget() = 0;
protected:
char _name[20];
private:
FileSelectionDialog *_mediator;
};
class List: public Widget
{
public:
List(FileSelectionDialog *dir, char *name): Widget(dir, name){}
void queryWidget()
{
cout << " " << _name << " list queried" << endl;
}
void updateWidget()
{
cout << " " << _name << " list updated" << endl;
}
};
class Edit: public Widget
{
public:
Edit(FileSelectionDialog *dir, char *name): Widget(dir, name){}
void queryWidget()
{
cout << " " << _name << " edit queried" << endl;
}
void updateWidget()
{
cout << " " << _name << " edit updated" << endl;
}
};
class FileSelectionDialog
{
public:
enum Widgets
{
FilterEdit, DirList, FileList, SelectionEdit
};
FileSelectionDialog()
{
_components[FilterEdit] = new Edit(this, "filter");
_components[DirList] = new List(this, "dir");
_components[FileList] = new List(this, "file");
_components[SelectionEdit] = new Edit(this, "selection");
}
virtual ~FileSelectionDialog();
void handleEvent(int which)
{
_components[which]->changed();
}
virtual void widgetChanged(Widget *theChangedWidget)
{
if (theChangedWidget == _components[FilterEdit])
{
_components[FilterEdit]->queryWidget();
_components[DirList]->updateWidget();
_components[FileList]->updateWidget();
_components[SelectionEdit]->updateWidget();
}
else if (theChangedWidget == _components[DirList])
{
_components[DirList]->queryWidget();
_components[FileList]->updateWidget();
_components[FilterEdit]->updateWidget();
_components[SelectionEdit]->updateWidget();
}
else if (theChangedWidget == _components[FileList])
{
_components[FileList]->queryWidget();
_components[SelectionEdit]->updateWidget();
}
else if (theChangedWidget == _components[SelectionEdit])
{
_components[SelectionEdit]->queryWidget();
cout << " file opened" << endl;
}
}
private:
Widget *_components[4];
};
FileSelectionDialog::~FileSelectionDialog()
{
for (int i = 0; i < 3; i++)
delete _components[i];
}
void Widget::changed()
{
_mediator->widgetChanged(this);
}
int main()
{
FileSelectionDialog fileDialog;
int i;
cout << "Exit[0], Filter[1], Dir[2], File[3], Selection[4]: ";
cin >> i;
while (i)
{
fileDialog.handleEvent(i - 1);
cout << "Exit[0], Filter[1], Dir[2], File[3], Selection[4]: ";
cin >> i;
}
}
Результаты вывода программы:
Exit[0], Filter[1], Dir[2], File[3], Selection[4]: 1
filter edit queried
dir list updated
file list updated
selection edit updated
Exit[0], Filter[1], Dir[2], File[3], Selection[4]: 2
dir list queried
file list updated
filter edit updated
selection edit updated
Exit[0], Filter[1], Dir[2], File[3], Selection[4]: 3
file list queried
selection edit updated
Exit[0], Filter[1], Dir[2], File[3], Selection[4]: 4
selection edit queried
file opened
Exit[0], Filter[1], Dir[2], File[3], Selection[4]: 3
file list queried
selection edit updated
Реализация паттерна Mediator: до и после
До
Объекты Node
взаимодействуют друг с другом напрямую, требуется рекурсия, неудобное удаление узла, при этом первый узел удалить не возможно.
class Node
{
public:
Node(int v)
{
m_val = v;
m_next = 0;
}
void add_node(Node *n)
{
if (m_next)
m_next->add_node(n);
else
m_next = n;
}
void traverse()
{
cout << m_val << " ";
if (m_next)
m_next->traverse();
else
cout << '\n';
}
void remove_node(int v)
{
if (m_next)
if (m_next->m_val == v)
m_next = m_next->m_next;
else
m_next->remove_node(v);
}
private:
int m_val;
Node *m_next;
};
int main()
{
Node lst(11);
Node two(22), thr(33), fou(44);
lst.add_node(&two);
lst.add_node(&thr);
lst.add_node(&fou);
lst.traverse();
lst.remove_node(44);
lst.traverse();
lst.remove_node(22);
lst.traverse();
lst.remove_node(11);
lst.traverse();
}
Результаты вывода программы:
11 22 33 44
11 22 33
11 33
11 33
После
"Посреднический" класс List
упрощает все административные функции, рекурсия исключена.
class Node
{
public:
Node(int v)
{
m_val = v;
}
int get_val()
{
return m_val;
}
private:
int m_val;
};
class List
{
public:
void add_node(Node *n)
{
m_arr.push_back(n);
}
void traverse()
{
for (int i = 0; i < m_arr.size(); ++i)
cout << m_arr[i]->get_val() << " ";
cout << '\n';
}
void remove_node(int v)
{
for (vector::iterator it = m_arr.begin();
it != m_arr.end(); ++it)
if ((*it)->get_val() == v)
{
m_arr.erase(it);
break;
}
}
private:
vector m_arr;
};
int main()
{
List lst;
Node one(11), two(22);
Node thr(33), fou(44);
lst.add_node(&one);
lst.add_node(&two);
lst.add_node(&thr);
lst.add_node(&fou);
lst.traverse();
lst.remove_node(44);
lst.traverse();
lst.remove_node(22);
lst.traverse();
lst.remove_node(11);
lst.traverse();
}
Результаты вывода программы:
11 22 33 44
11 22 33
11 33
33