«1. Обзор

В этой статье мы рассмотрим паттерн посредника, один из поведенческих паттернов GoF. Мы опишем его назначение и объясним, когда мы должны его использовать.

Как обычно, мы также приведем простой пример кода.

2. Шаблон посредника

В объектно-ориентированном программировании мы всегда должны пытаться проектировать систему таким образом, чтобы компоненты были слабо связаны и могли использоваться повторно. Такой подход упрощает поддержку и тестирование нашего кода.

Однако в реальной жизни нам часто приходится иметь дело со сложным набором зависимых объектов. В этом случае вам может пригодиться паттерн посредника.

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

Это способствует слабой связи, так как набор компонентов, работающих вместе, больше не должен взаимодействовать напрямую. Вместо этого они относятся только к одному объекту-посреднику. Таким образом, также проще повторно использовать эти объекты в других частях системы.

3. UML-диаграмма шаблона посредника

Давайте теперь посмотрим на шаблон визуально:

На приведенной выше диаграмме UML мы можем идентифицировать следующих участников:

    Посредник определяет интерфейс, который используют объекты Colleague. для связи Colleague определяет абстрактный класс, содержащий единственную ссылку на Mediator. ConcreteMediator инкапсулирует логику взаимодействия между объектами Colleague. ConcreteColleague1 и ConcreteColleague2 взаимодействуют только через Mediator

Как мы видим, объекты 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. Добавление шаблона посредника

Теперь давайте реализуем шаблон посредника, чтобы уменьшить зависимости между нашими классами и сделать код более пригодным для повторного использования.

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();
    }
}

Во-первых, давайте представим класс Mediator:

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. Заключение