Введение
Завершаем обзор отдельных паттернов парой Chain of Responsibility (передача запроса по цепочке потенциальных обработчиков до первого, способного его обработать) и Mediator (централизация взаимодействия между множеством объектов через единый посредник, избегающий их прямой, множественной взаимной связанности).
Концепция
Chain of Responsibility связывает несколько объектов-обработчиков в цепочку, и запрос передаётся последовательно по этой цепочке от одного обработчика к другому, пока какой-либо из них не решит обработать запрос (или, в некоторых вариантах, каждый обработчик в цепочке выполняет свою часть обработки, и запрос продолжает движение по цепочке независимо) — это даёт гибкость в динамическом изменении состава и порядка обработчиков без изменения кода, инициирующего запрос. Mediator вводит отдельный объект-посредник, через который взаимодействуют несколько других объектов, вместо их прямой, попарной связи друг с другом — это особенно ценно, когда количество взаимодействующих объектов велико, и попарные прямые связи между всеми ними образовали бы трудно поддерживаемую, сильно связанную структуру («каждый знает о каждом»), тогда как Mediator централизует это взаимодействие, и каждый отдельный объект знает только о посреднике, а не о множестве других, потенциально взаимодействующих с ним объектов.
Пример кода
#include <iostream>
#include <memory>
// Chain of Responsibility — обработка запроса поддержки разными уровнями специалистов
class SupportHandler
{
public:
void setNext(std::shared_ptr<SupportHandler> next) { m_next = next; }
virtual void handle(int severityLevel)
{
if (m_next) m_next->handle(severityLevel); // передача ДАЛЬШЕ по цепочке, если сам не обработал
}
virtual ~SupportHandler() = default;
protected:
std::shared_ptr<SupportHandler> m_next;
};
class FirstLineSupport : public SupportHandler
{
public:
void handle(int severityLevel) override
{
if (severityLevel <= 1) {
std::cout << "Обработано первой линией поддержки" << std::endl;
} else {
SupportHandler::handle(severityLevel); // передача ДАЛЬШЕ — этот уровень не справился
}
}
};
class SeniorEngineer : public SupportHandler
{
public:
void handle(int severityLevel) override
{
if (severityLevel <= 3) {
std::cout << "Обработано старшим инженером" << std::endl;
} else {
SupportHandler::handle(severityLevel);
}
}
};
int main()
{
auto firstLine = std::make_shared<FirstLineSupport>();
auto senior = std::make_shared<SeniorEngineer>();
firstLine->setNext(senior); // построение ЦЕПОЧКИ
firstLine->handle(1); // обработано firstLine
firstLine->handle(3); // firstLine НЕ справился -> передано senior -> обработано
return 0;
}
// Mediator — централизация взаимодействия между несколькими UI-компонентами
#include <QObject>
#include <QLineEdit>
#include <QPushButton>
class FormMediator : public QObject // Mediator — ЕДИНСТВЕННАЯ точка координации между полями формы
{
Q_OBJECT
public:
FormMediator(QLineEdit *nameField, QPushButton *submitButton)
: m_nameField(nameField), m_submitButton(submitButton)
{
connect(m_nameField, &QLineEdit::textChanged, this, &FormMediator::onNameChanged);
// nameField и submitButton НЕ знают друг о друге НАПРЯМУЮ — оба взаимодействуют ТОЛЬКО через Mediator
}
private slots:
void onNameChanged(const QString &text)
{
m_submitButton->setEnabled(!text.isEmpty()); // координация — Mediator решает, КАК поля влияют друг на друга
}
private:
QLineEdit *m_nameField;
QPushButton *m_submitButton;
};
Пояснения к коду
FirstLineSupport::handle(), проверяющий severityLevel <= 1 и, если условие не выполнено, вызывающий SupportHandler::handle() (передающий запрос следующему обработчику в цепочке), показывает суть Chain of Responsibility — каждый обработчик решает самостоятельно, способен ли он обработать конкретный запрос, и если нет, явно передаёт его дальше, что позволяет легко добавлять новые уровни обработки (новые классы, реализующие SupportHandler) или изменять их порядок через setNext(), не изменяя код инициатора запроса. FormMediator, координирующий nameField и submitButton (оба ничего не знающие друг о друге напрямую), показывает практичное применение Mediator в Qt-коде — без посредника QLineEdit пришлось бы напрямую знать о QPushButton (или наоборот) для реализации той же логики «кнопка активна только если поле не пустое», что создало бы прямую, нежелательную связанность между двумя, в остальном независимыми виджетами интерфейса.
Подводные камни
- Слишком длинная цепочка обработчиков Chain of Responsibility без гарантии, что запрос РЕАЛЬНО будет обработан кем-то из них — если ни один обработчик в цепочке не способен обработать конкретный, особенно нетипичный запрос, он просто «проваливается» через всю цепочку без какой-либо обработки, и качественная реализация должна явно предусматривать обработку этого случая (например, явный финальный обработчик «по умолчанию» в конце цепочки, или явная проверка и логирование необработанных запросов), а не оставлять эту ситуацию без какой-либо диагностики.
- Превращение Mediator в «божественный объект» (god object antipattern, аналогично подводному камню про Facade), постепенно вбирающий в себя всё больше несвязанной логики координации множества разных, в остальном не связанных частей интерфейса или системы — для действительно сложных, многокомпонентных приложений единственный, монолитный Mediator для абсолютно ВСЕХ взаимодействий может стать столь же трудным для понимания и поддержки, как и устранённая им сложная сеть прямых связей, и для крупных систем может быть оправдано несколько более специализированных, сфокусированных Mediator-объектов вместо одного -объединяющего.
- Незнание, что Mediator концептуально пересекается (а иногда прямо реализуется через) Observer — во многих практических Qt-реализациях Mediator-паттерн фактически строится на основе сигналов/слотов (как показывает пример с
FormMediator), и понимание этой связи помогает увидеть, что многие архитектурные задачи координации между объектами могут решаться комбинацией уже известных, более фундаментальных механизмов (сигналы/слоты как реализация Observer), а не требуют изобретения принципиально новой инфраструктуры специально для Mediator. - Применение Chain of Responsibility для случая, где статически известный, фиксированный порядок проверки нескольких условий лучше выражается простой последовательностью
if/else if— паттерн ценен именно тогда, когда состав и порядок обработчиков должен быть динамически конфигурируемым или легко расширяемым извне (как добавление нового, не предвиденного заранее типа обработчика без изменения существующего кода), и для небольшого, стабильного, заранее полностью известного набора условий обычная условная конструкция может быть значительно проще и понятнее.