1. Обзор
В этой статье мы рассмотрим паттерн посредника, один из поведенческих паттернов GoF . Мы опишем его назначение и объясним, когда мы должны его использовать.
Как обычно, мы также приведем простой пример кода.
2. Паттерн посредника
В объектно-ориентированном программировании мы всегда должны пытаться проектировать систему таким образом, чтобы компоненты были слабо связаны и допускали повторное использование . Такой подход упрощает поддержку и тестирование нашего кода.
Однако в реальной жизни нам часто приходится иметь дело со сложным набором зависимых объектов. В этом случае вам может пригодиться паттерн посредника.
Целью шаблона посредника является уменьшение сложности и зависимости между тесно связанными объектами, взаимодействующими напрямую друг с другом . Это достигается путем создания объекта-посредника, который заботится о взаимодействии между зависимыми объектами. Следовательно, все общение идет через посредника.
Это способствует слабой связи, поскольку набору компонентов, работающих вместе, больше не нужно взаимодействовать напрямую. Вместо этого они относятся только к одному объекту-посреднику. Таким образом, также проще повторно использовать эти объекты в других частях системы.
3. UML-диаграмма шаблона посредника
Давайте теперь посмотрим на шаблон визуально:
На приведенной выше диаграмме UML мы можем определить следующих участников:
Посредник
определяет интерфейс, который объектыColleague
используют для связи .Коллега
определяет абстрактный класс, содержащий единственную ссылку напосредника .
ConcreteMediator
инкапсулирует логику взаимодействия между объектамиColleague .
ConcreteColleague1
иConcreteColleague2
взаимодействуют только черезпосредника .
Как мы видим, объекты Colleague
не ссылаются друг на друга напрямую. Вместо этого все общение осуществляется посредником .
Следовательно, ConcreteColleague1
и ConcreteColleague2
легче использовать повторно.
Кроме того, если нам нужно изменить способ совместной работы объектов Colleague
, нам нужно только изменить логику ConcreteMediator .
Или мы можем создать новую реализацию Медиатора.
4. Реализация Java
Теперь, когда у нас есть четкое представление о теории, давайте рассмотрим пример, чтобы лучше понять концепцию на практике.
4.1. Пример сценария
Представьте, что мы создаем простую систему охлаждения, состоящую из вентилятора, блока питания и кнопки. Нажатие кнопки либо включает, либо выключает вентилятор. Прежде чем мы включим вентилятор, нам нужно включить питание. Точно так же мы должны отключить питание сразу после выключения вентилятора.
Давайте теперь посмотрим на пример реализации:
public class Button {
private Fan fan;
// constructor, getters and setters
public void press(){
if(fan.isOn()){
fan.turnOff();
} else {
fan.turnOn();
}
}
}
public class Fan {
private Button button;
private PowerSupplier powerSupplier;
private boolean isOn = false;
// constructor, getters and setters
public void turnOn() {
powerSupplier.turnOn();
isOn = true;
}
public void turnOff() {
isOn = false;
powerSupplier.turnOff();
}
}
public class PowerSupplier {
public void turnOn() {
// implementation
}
public void turnOff() {
// implementation
}
}
Далее проверим работоспособность:
@Test
public void givenTurnedOffFan_whenPressingButtonTwice_fanShouldTurnOnAndOff() {
assertFalse(fan.isOn());
button.press();
assertTrue(fan.isOn());
button.press();
assertFalse(fan.isOn());
}
Кажется, все работает нормально. Но обратите внимание, как тесно связаны классы Button, Fan
и PowerSupplier
. Кнопка воздействует непосредственно на вентилятор
, а вентилятор
взаимодействует как с кнопкой
, так и с источником
питания.
Было бы сложно повторно использовать класс Button в других модулях.
Кроме того, если нам нужно добавить в нашу систему второй источник питания, нам придется изменить логику класса Fan .
4.2. Добавление шаблона посредника
Теперь давайте реализуем шаблон посредника, чтобы уменьшить зависимости между нашими классами и сделать код более пригодным для повторного использования.
Во-первых, давайте представим класс Mediator :
public class Mediator {
private Button button;
private Fan fan;
private PowerSupplier powerSupplier;
// constructor, getters and setters
public void press() {
if (fan.isOn()) {
fan.turnOff();
} else {
fan.turnOn();
}
}
public void start() {
powerSupplier.turnOn();
}
public void stop() {
powerSupplier.turnOff();
}
}
Далее изменим оставшиеся классы:
public class Button {
private Mediator mediator;
// constructor, getters and setters
public void press() {
mediator.press();
}
}
public class Fan {
private Mediator mediator;
private boolean isOn = false;
// constructor, getters and setters
public void turnOn() {
mediator.start();
isOn = true;
}
public void turnOff() {
isOn = false;
mediator.stop();
}
}
Опять же, давайте проверим функциональность:
@Test
public void givenTurnedOffFan_whenPressingButtonTwice_fanShouldTurnOnAndOff() {
assertFalse(fan.isOn());
button.press();
assertTrue(fan.isOn());
button.press();
assertFalse(fan.isOn());
}
Наша система охлаждения работает, как и ожидалось.
Теперь, когда мы реализовали шаблон посредника, ни один из классов Button
, Fan
или PowerSupplier не
взаимодействует напрямую . У них есть только одна ссылка на Посредника.
Если в будущем нам потребуется добавить второй источник питания, все, что нам нужно сделать, это обновить логику Mediator ;
Классы Button
и Fan
остаются нетронутыми.
Этот пример показывает, как легко мы можем отделить зависимые объекты и упростить обслуживание нашей системы.
5. Когда использовать шаблон медиатора
Шаблон посредника — хороший выбор, если нам приходится иметь дело с набором тесно связанных объектов, которые трудно поддерживать. Таким образом, мы можем уменьшить зависимости между объектами и снизить общую сложность.
Кроме того, с помощью объекта-посредника мы извлекаем коммуникационную логику в один компонент, поэтому мы следуем принципу единой ответственности . Кроме того, мы можем вводить новые посредники без необходимости изменения остальных частей системы. Следовательно, мы следуем принципу открытого-закрытого.
Однако иногда у нас может быть слишком много тесно связанных объектов из-за неправильной конструкции системы. Если это так, мы не должны применять паттерн посредника . Вместо этого мы должны сделать один шаг назад и переосмыслить то, как мы смоделировали наши классы.
Как и в случае со всеми другими паттернами, нам необходимо рассмотреть конкретный вариант использования, прежде чем слепо внедрять паттерн посредника .
6. Заключение
В этой статье мы узнали о шаблоне медиатора. Мы объяснили, какую проблему решает этот шаблон и когда нам действительно следует рассмотреть возможность его использования. Мы также реализовали простой пример шаблона проектирования.
Как всегда, полные примеры кода доступны на GitHub .