Объектно-ориентированные шаблоны проектирования Mediator против Observer

95

Я читал « Банду четырех» , чтобы решить некоторые из моих проблем, и наткнулся на шаблон « Посредник» .

Раньше я использовал Observer в своих проектах для создания приложения с графическим интерфейсом. Я немного сбит с толку, так как не вижу большой разницы между ними. Я просмотрел, чтобы найти разницу, но не смог найти подходящего ответа на свой запрос.

Может ли кто-нибудь помочь мне провести различие между ними на каком-нибудь хорошем примере, который четко их разграничивает?

Fooo
источник
5
Моя просьба перенести этот вопрос на Programmers.StackExchangeбыла отклонена, но я сделал похожий пост, потому что меня интересовал ответ. Вы можете найти некоторые ответы интересными. :)
Рэйчел
Для примеров JavaScript вы можете взглянуть на мой ответ на аналогичный вопрос .
Alex Pakka
Оригинальные GoF книги адреса , которые в соответствии с точкой раздела реализации № 8, давая пример ChangeManagerдля Observerшаблона , который использует Mediator. увидеть; paginas.fe.up.pt/~aaguiar/as/gof/hires/pat5g.htm#samplecode
robi-y

Ответы:

106

Шаблон наблюдателя: определяет зависимость «один ко многим» между объектами, так что, когда один объект меняет состояние, все его зависимые объекты автоматически уведомляются и обновляются.

Шаблон посредника: определите объект, который инкапсулирует, как взаимодействует набор объектов. Посредник способствует слабой связи, не позволяя объектам явно ссылаться друг на друга, и позволяет вам изменять их взаимодействие независимо.

Источник: dofactory

Пример:

Шаблон наблюдателя: класс A, может иметь ноль или более зарегистрированных наблюдателей типа O. Когда что-то в A изменяется, это уведомляет всех наблюдателей.

Шаблон посредника: у вас есть некоторое количество экземпляров класса X (или, возможно, даже несколько разных типов: X, Y и Z), и они хотят общаться друг с другом (но вы не хотите, чтобы каждый имел явные ссылки на каждый other), поэтому вы создаете класс-посредник M. Каждый экземпляр X имеет ссылку на общий экземпляр M, через который он может взаимодействовать с другими экземплярами X (или X, Y и Z).

CDC
источник
объяснение наблюдателя, кажется, близко к шаблону команд, а не шаблону наблюдателя
Аун
41

В оригинальной книге, в которой были введены термины «наблюдатель» и «посредник», « шаблоны проектирования», «элементы многоразового объектно-ориентированного программного обеспечения» , говорится, что шаблон «посредник» может быть реализован с использованием шаблона «наблюдатель». Однако это также может быть реализовано, если у коллег (которые примерно эквивалентны субъектам шаблона наблюдателя) есть ссылка либо на класс посредника, либо на интерфейс посредника.

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

Посредник немного более конкретен, он избегает прямого общения классов, а вместо этого через посредника. Это помогает принципу единой ответственности, позволяя передавать коммуникацию классу, который просто занимается коммуникацией.

Классический пример посредника находится в графическом интерфейсе, где наивный подход может привести к появлению кода события нажатия кнопки, говорящего: «если панель Foo отключена, а панель Bar имеет метку с надписью« Пожалуйста, введите дату », тогда не вызывайте сервер, в противном случае - вперед », где с шаблоном« Посредник »можно было бы сказать:« Я всего лишь кнопка и не имею никаких земных дел, зная о панели Foo и ярлыке на панели Bar, поэтому я просто спрошу своего посредника, звонит ли сервер. в порядке прямо сейчас ".

Или, если Посредник реализован с использованием паттерна Наблюдатель, кнопка будет говорить «Привет, наблюдатели (включая посредника), мое состояние изменилось (кто-то щелкнул меня). Сделайте что-нибудь с этим, если вам не все равно». В моем примере это, вероятно, имеет меньше смысла, чем прямая ссылка на посредника, но во многих случаях использование шаблона Observer для реализации Mediator будет иметь смысл, и разница между Observer и Mediator будет больше связана с намерением, чем с различием в самом коде.

psr
источник
Я искал это слово «принцип единой ответственности».
stdout
37

Наблюдатель

1. Без

  • Client1 : Привет, тема , когда ты меняешься ?

  • Client2 : Когда вы сменили тему ? Я не заметил!

  • Клиент3 : Я знаю, что тема изменилась.

2. С

  • Клиенты молчат.
  • Немного позже ...
  • Тема : Уважаемые клиенты , я изменился!

Посредник

1. Без

  • Client1 : Привет, Taxi1 , отвези меня куда-нибудь.
  • Клиент2 : Привет, Такси1 , отвези меня куда-нибудь.
  • Клиент1 : Привет, Такси2 , отвези меня куда-нибудь.
  • Клиент2 : Привет, Такси2 , отвези меня куда-нибудь.

2. С

  • Client1 : Привет, TaxiCenter , пожалуйста, возьмите мне такси .
  • Client2 : Привет, TaxiCenter , пожалуйста, возьми мне такси .
Pmpr
источник
2
Ваш пример посредника - это заводской шаблон, а не шаблон посредника
Мохаммад Карими
2
@Pmpr Это шаблон проектирования посредника. TaxiCenter не будет создавать такси, он каким-то образом делает их доступными (вероятно, каждое такси ждет, пока TaxiCenter не скажет, что ваша очередь)
Siva R
14

Эти шаблоны используются в разных ситуациях:

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

Шаблон наблюдателя используется, когда класс хочет разрешить другим классам регистрироваться и получать уведомления о событиях, например, ButtonListener и т. Д.

Оба этих шаблона допускают меньшее сцепление, но они совершенно разные.

Узилан
источник
7

Давайте рассмотрим пример: представьте, что вы хотите создать два приложения:

  1. Приложение для чата.
  2. Приложение оператора скорой помощи.

посредник

Создавая приложение для чата, вы будете выбирать mediatorшаблон проектирования.

  • Люди могут присоединяться к чату и выходить из него в любой момент, поэтому нет никакого смысла поддерживать прямую связь между двумя людьми, разговаривающими в чате.
  • Нам все еще нужно облегчить общение между двумя людьми и позволить им общаться в чате.

Почему мы предпочтем mediator? просто взгляните на его определение:

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

Как работает магия? Сначала мы создадим посредника чата и заставим объекты лиц регистрироваться в нем, чтобы у него было двухстороннее соединение с каждым отдельным человеком (человек может отправлять сообщение с помощью посредника чата, потому что у него есть доступ к нему, а посредник чата будет иметь доступ к нему). полученный метод объекта человека, потому что он также имеет к нему доступ)

function Person(name) {
    let self = this;
    this._name = name;
    this._chat = null;

    this._receive(from, message) {        
        console.log("{0}: '{1}'".format(from.name(), message));
    }
    this._send(to, message) {
        this._chat.message(this, to, message);
    }
    return {
        receive: (from, message) => { self._receive(from, message) },
        send: (to, message) => { self._send(to, message) },
        initChat: (chat) => { this._chat = chat; },
        name: () => { return this._name; }
    }
}


function ChatMediator() {
    let self = this;
    this._persons = [];    

    return {
        message: function (from, to, message) {
            if (self._persons.indexOf(to) > -1) {
                self._persons[to].receive(from, message);
            }
        },
        register: function (person) {
            person.initChat(self);
            self._persons.push(person);
        }
        unRegister: function (person) {
            person.initChat(null);
            delete self._persons[person.name()];
        }
    }
};

//Usage:
let chat = new ChatMediator();

let colton = new Person('Colton');
let ronan = new Person('Ronan');

chat.register(colton);
chat.register(ronan);

colton.send(colton, 'Hello there, nice to meet you');
ronan.send(ronan, 'Nice to meet you to');

colton.send(colton, 'Goodbye!');
chat.unRegister(colton);

наблюдатель

Создавая приложение для вызова службы экстренной помощи, вы будете выбирать observerшаблон проектирования.

  • Каждый observerобъект скорой помощи хочет быть проинформирован о возникновении чрезвычайной ситуации, чтобы он мог проехать по адресу и оказать помощь.
  • Оператор службы экстренной помощи observableотслеживает каждую машину скорой помощи observersи уведомляет их, когда требуется помощь (или возникает событие).

Почему мы предпочтем observer? просто взгляните на его определение:

Объект, называемый субъектом, поддерживает список своих иждивенцев, называемых наблюдателями, и автоматически уведомляет их о любых изменениях состояния, обычно вызывая один из их методов.

function AmbulanceObserver(name) {
    let self = this;
    this._name = name;
    this._send(address) {
        console.log(this._name + ' has been sent to the address: ' + address);
    }
    return {
        send: (address) => { self._send(address) },
        name: () => { return this._name; }
    }
}


function OperatorObservable() {
    let self = this;
    this._ambulances = [];    

    return {
        send: function (ambulance, address) {
            if (self._ambulances.indexOf(ambulance) > -1) {
                self._ambulances[ambulance].send(address);
            }
        },
        register: function (ambulance) {
            self._ambulances.push(ambulance);
        }
        unRegister: function (ambulance) {
            delete self._ambulances[ambulance.name()];
        }
    }
};

//Usage:
let operator = new OperatorObservable();

let amb111 = new AmbulanceObserver('111');
let amb112 = new AmbulanceObserver('112');

operator.register(amb111);
operator.register(amb112);

operator.send(amb111, '27010 La Sierra Lane Austin, MN 000');
operator.unRegister(amb111);

operator.send(amb112, '97011 La Sierra Lane Austin, BN 111');
operator.unRegister(amb112);

Различия:

  1. В чате mediatorесть двусторонняя связь между объектами-людьми (отправка и получение), тогда как у оператора observableесть только односторонняя связь (он говорит машине скорой помощи observerехать и финишировать).
  2. Чат mediatorможет заставить объекты людей взаимодействовать между ними (даже если это не прямое общение), машины скорой помощи observersрегистрируются только на observableсобытия оператора .
  3. Каждый объект человека имеет ссылку на чат mediator, а также чат mediatorсохраняет ссылку на каждого человека. Там, где скорая помощь observerне ссылается на оператора observable, только оператор observableссылается на каждую машину скорой помощи observer.
Шахар Шокрани
источник
3
Последний бит помогает. Посредник и наблюдатель достигают одной и той же цели, однако посредник обеспечивает двустороннюю связь, в то время как наблюдатель работает только в одном направлении.
kiwicomb123 03
Точно, рад, что это помогло
Шахар Шокрани 03
Когда я читал заголовок, у меня было с точностью до наоборот: на самом деле колл-центр скорой помощи является посредником - он принимает вызов, а затем отправляет скорую помощь, полицию и пожарных. И каждая скорая помощь на радиостанции да, это наблюдатель - они ждут события по радио. что их трогает В чате: сервер - посредник, каждый клиент - наблюдатель. Как вам кажется?
Макс
7

Хотя оба они используются для организованного способа сообщения об изменениях состояния, они немного отличаются структурно и семантически IMO.

Наблюдатель используется для трансляции изменения состояния конкретного объекта из самого объекта. Таким образом, изменение происходит в центральном объекте, который также отвечает за передачу сигналов. Однако в Mediator изменение состояния может произойти в любом объекте, но оно транслируется посредником. Так что есть разница в потоке. Но я не думаю, что это влияет на поведение нашего кода. Мы можем использовать одно или другое для достижения того же поведения. С другой стороны, это различие может повлиять на концептуальное понимание кода.

Видите ли, основная цель использования шаблонов - это скорее создание общего языка между разработчиками. Итак, когда я вижу посредника, я лично понимаю, что несколько элементов пытаются взаимодействовать через одного брокера / концентратора, чтобы уменьшить коммуникационный шум (или продвигать SRP), и каждый объект одинаково важен с точки зрения способности сигнализировать об изменении состояния. Например, представьте, что к аэропорту приближается несколько самолетов. Каждый должен общаться через пилон (посредник), а не друг с другом. (Представьте, что 1000 самолетов связываются друг с другом при посадке - это было бы беспорядком)

Однако, когда я вижу наблюдателя, это означает, что есть некоторые изменения состояния, которые меня могут волновать, и я должен зарегистрироваться / подписаться, чтобы прослушивать определенные изменения состояния. За сигнализацию изменений состояния отвечает центральный объект. Например, если мне важен конкретный аэропорт на пути из пункта А в пункт Б, я могу зарегистрироваться в этом аэропорту, чтобы транслировать некоторые события, например, пустая взлетно-посадочная полоса или что-то в этом роде.

Надеюсь, это ясно.

стандартный вывод
источник
5

@cdc превосходно объяснил разницу в намерениях.

Я добавлю немного информации поверх него.

Наблюдатель : включает уведомление о событии в одном объекте для разных наборов объектов (экземпляров разных классов)

Посредник : Централизуйте связь между набором объектов, созданных из определенного класса.

Структура паттерна Медиатор от дофабрики :

введите описание изображения здесь

Посредник : определяет интерфейс для связи между коллегами.

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

ConcreteMediator : реализует кооперативное поведение, координируя объекты коллег и поддерживая своих коллег.

ConcreteColleague : реализует операции уведомления, полученные через посредника , которые были созданы другим коллегой.

Один пример из реального мира:

Вы обслуживаете сеть компьютеров в топологии Mesh . Если добавлен новый компьютер или удален существующий компьютер, все остальные компьютеры в этой сети должны знать об этих двух событиях.

Посмотрим, как в него вписывается паттерн Посредник.

Фрагмент кода:

import java.util.List;
import java.util.ArrayList;

/* Define the contract for communication between Colleagues. 
   Implementation is left to ConcreteMediator */
interface Mediator{
    public void register(Colleague colleague);
    public void unregister(Colleague colleague);
}
/* Define the contract for notification events from Mediator. 
   Implementation is left to ConcreteColleague
*/
abstract class Colleague{
    private Mediator mediator;
    private String name;

    public Colleague(Mediator mediator,String name){
        this.mediator = mediator;
        this.name = name;
    }
    public String toString(){
        return name;
    }
    public abstract void receiveRegisterNotification(Colleague colleague);
    public abstract void receiveUnRegisterNotification(Colleague colleague);    
}
/*  Process notification event raised by other Colleague through Mediator.   
*/
class ComputerColleague extends Colleague {
    private Mediator mediator;

    public ComputerColleague(Mediator mediator,String name){
        super(mediator,name);
    }
    public  void receiveRegisterNotification(Colleague colleague){
        System.out.println("New Computer register event with name:"+colleague+
        ": received @"+this);
        // Send further messages to this new Colleague from now onwards
    }
    public  void receiveUnRegisterNotification(Colleague colleague){
        System.out.println("Computer left unregister event with name:"+colleague+
        ":received @"+this);
        // Do not send further messages to this Colleague from now onwards
    }
}
/* Act as a central hub for communication between different Colleagues. 
   Notifies all Concrete Colleagues on occurrence of an event
*/
class NetworkMediator implements Mediator{
    List<Colleague> colleagues = new ArrayList<Colleague>();

    public NetworkMediator(){

    }

    public void register(Colleague colleague){
        colleagues.add(colleague);
        for (Colleague other : colleagues){
            if ( other != colleague){
                other.receiveRegisterNotification(colleague);
            }
        }
    }
    public void unregister(Colleague colleague){
        colleagues.remove(colleague);
        for (Colleague other : colleagues){
            other.receiveUnRegisterNotification(colleague);
        }
    }
}

public class MediatorPatternDemo{
    public static void main(String args[]){
        Mediator mediator = new NetworkMediator();
        ComputerColleague colleague1 = new ComputerColleague(mediator,"Eagle");
        ComputerColleague colleague2 = new ComputerColleague(mediator,"Ostrich");
        ComputerColleague colleague3 = new ComputerColleague(mediator,"Penguin");
        mediator.register(colleague1);
        mediator.register(colleague2);
        mediator.register(colleague3);
        mediator.unregister(colleague1);
    }
}

выход:

New Computer register event with name:Ostrich: received @Eagle
New Computer register event with name:Penguin: received @Eagle
New Computer register event with name:Penguin: received @Ostrich
Computer left unregister event with name:Eagle:received @Ostrich
Computer left unregister event with name:Eagle:received @Penguin

Пояснение:

  1. Орел добавляется в сеть сначала через событие регистрации. Никаких уведомлений другим коллегам, так как Eagle - первый.
  2. Когда Ostrich добавлен в сеть, Eagle получает уведомление: теперь выводится строка 1 вывода.
  3. Когда Penguin добавлен в сеть, и Eagle, и Ostrich были уведомлены: теперь отображается строка 2 и строка 3 вывода.
  4. Когда Eagle покинул сеть в результате отмены регистрации, были уведомлены и Ostrich, и Penguin . Строка 4 и строка 5 вывода теперь визуализируются.
Равиндра бабу
источник
2

Как насчет этого объяснения Технически Observer и Mediator одинаковы и используются для обеспечения независимого взаимодействия компонентов, но их использование отличается.

В то время как obeserver уведомляет подписанные компоненты об изменениях состояния (например, о создании новой записи в базе данных), mediator команды зарегистрировали компоненты для выполнения каких-либо действий, связанных с потоком бизнес-логики (отправка электронной почты пользователю для сброса пароля).

Наблюдатель

  • Потребители уведомлений несут ответственность за подписку, чтобы получать уведомления
  • Обработка уведомлений не является частью бизнес-потока

Посредник

  • Для связи "издателя" и "потребителей" требуется явная регистрация.
  • Обработка уведомлений является частью конкретного бизнес-потока
Сатурн Технологии
источник