Посередник проти спостерігача об’єктно-орієнтовані шаблони дизайну


92

Я читав " Банду чотирьох" , щоб вирішити деякі мої проблеми, і натрапив на зразок Посередника .

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

Чи може хтось допомогти мені розрізнити їх на якомусь хорошому прикладі, який чітко розмежовує їх?


5
Моє прохання перенести це запитання Programmers.StackExchangeбуло відхилено, але я зробив там подібний допис , оскільки зацікавив відповідь. Деякі відповіді можуть вам здатися цікавими. :)
Рейчел

Для прикладів JavaScript ви можете поглянути на мою відповідь на подібне запитання .
Alex Pakka,

Оригінальна книга GoF звертається до цього у розділі реалізації, пункт 8, наводячи приклад a ChangeManagerдля Observerшаблону, який використовує Mediator. побачити; paginas.fe.up.pt/~aaguiar/as/gof/hires/pat5g.htm#samplecode
-y

Відповіді:


104

Шаблон спостерігача: Визначає залежність "один до багатьох" між об'єктами, щоб, коли один об'єкт змінює стан, усі його залежні сповіщалися та оновлювались автоматично.

Шаблон посередника: Визначте об’єкт, який інкапсулює взаємодію набору об’єктів. Посередник сприяє вільному зчепленню, утримуючи предмети від явного посилання один на одного, і він дозволяє змінювати їх взаємодію незалежно.

Джерело: dofactory

Приклад:

Схема спостерігача: Клас A, може мати зареєстрованих нуль або більше спостерігачів типу O. Коли щось в А змінюється, воно повідомляє всіх спостерігачів.

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


Пояснення спостерігача схоже на шаблон команди, а не на шаблон спостерігача
Aun

40

В оригінальній книзі, яка створила терміни «Спостерігач і посередник», « Шаблони дизайну», «Елементи багаторазового об’єктно-орієнтованого програмного забезпечення» , сказано, що шаблон «Медіатор» може бути реалізований за допомогою шаблону спостерігача. Однак це також може бути реалізовано, якщо колеги (які приблизно еквівалентні Суб'єктам моделі Observer) мають посилання або на клас Посередника, або на інтерфейс Посередника.

Є багато випадків, коли ви хочете використовувати шаблон спостерігача, ключовим є те, що об'єкт не повинен знати, які інші об'єкти спостерігають його стан.

Посередник трохи більш конкретний, він уникає того, щоб класи спілкувались безпосередньо, а натомість через посередника. Це допомагає принципу єдиної відповідальності, дозволяючи комунікацію розвантажувати до класу, який просто обробляє спілкування.

Класичний приклад посередника - в графічному інтерфейсі, де наївний підхід може призвести до коду на події натискання кнопки, що говорить "якщо панель Foo відключена, а панель панелі має мітку" Будь ласка, введіть дату ", тоді не дзвоніть на сервер, інакше вперед ", де за шаблоном" Посередник "це могло б сказати" Я просто кнопка і не маю жодного земного справи, знаючи про панель Foo та ярлик на панелі Bar, тому я просто запитаю свого посередника, чи викликає сервер прямо зараз ".

Або, якщо Медіатор реалізований із використанням шаблону Спостерігача, на кнопці буде сказано: "Гей, спостерігачі (що включатиме посередника), мій стан змінився (хтось натиснув мене). Зробіть щось із цим, якщо вам все одно". У моєму прикладі це, мабуть, має менший сенс, ніж безпосереднє посилання на посередника, але в багатьох випадках використання шаблону Observer для реалізації Mediator має сенс, і різниця між Observer та Mediator буде скоріше однією з цілей, ніж різницею в самому коді.


Я шукав це слово "Принцип єдиної відповідальності".
stdout

37

Спостерігач

1. Без

  • Клієнт1 : Привіт, суб’єкт , коли ти змінюєшся ?

  • Клієнт2 : Коли ви змінили тему ? Я не помітив!

  • Клієнт3 : Я знаю, що Тема змінилася.

2. З

  • Клієнти мовчать.
  • Через деякий час ...
  • Тема : Шановні клієнти , я змінився!

Посередник

1. Без

  • Клієнт1 : Гей, таксі1 , приведи мене куди.
  • Клієнт2 : Гей, таксі1 , підвези мене куди.
  • Клієнт1 : Гей, таксі2 , приведи мене куди.
  • Клієнт2 : Гей, таксі2 , приведи мене куди.

2. З

  • Клієнт1 : Гей, таксіцентр , будь ласка, візьми мені таксі .
  • Клієнт2 : Гей, таксіцентр , будь ласка, візьми мені таксі .

2
Вашим прикладом посередника є фабричний шаблон, а не шаблон посередника
Мохаммад Карімі

2
@Pmpr Це шаблон дизайну посередника. TaxiCenter не створює таксі, він надає таксі якимось чином (можливо, кожне таксі чекає, поки TaxiCenter скаже, що настала ваша черга)
Siva R

14

Ці шаблони використовуються в різних ситуаціях:

Шаблон медіатора використовується, коли у вас є дві підсистеми з певною залежністю, і одна з них має бути змінена, і оскільки ви, можливо, не захочете змінювати систему, яка залежить від іншої, можливо, ви захочете запровадити медіатора, який буде відокремити залежність між ними. Таким чином, коли одна із підсистем змінюється, все, що вам потрібно зробити, це оновити посередник.

Шаблон спостерігача використовується, коли клас хоче дозволити іншим класам реєструватися та отримувати сповіщення про події, наприклад ButtonListener тощо.

Обидві ці моделі дозволяють меншу взаємодію, але досить різні.


6

Розглянемо приклад: розглянемо, що ви хочете створити два додатки:

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

спостерігач

Створюючи програму дзвінка 911, ви будете вибирати 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

Точно, радий, що це допомогло
Шахар Шокрані

6

Хоча обидва вони використовуються для організованого способу розповісти про зміни в державі, вони дещо відрізняються в структурі та семантиці ІМО.

Спостерігач використовується для трансляції зміни стану певного об'єкта з самого об'єкта. Отже, зміна відбувається в центральному об’єкті, який також відповідає за його сигналізацію. Однак у Медіаторі зміна стану може відбуватися у будь-якому об’єкті, але це транслюється від посередника. Отже, є різниця в потоці. Але, я не думаю, що це впливає на нашу поведінку коду. Ми можемо використовувати те чи інше для досягнення однакової поведінки. З іншого боку, ця різниця може вплинути на концептуальне розуміння коду.

Дивіться, головна мета використання шаблонів - це швидше створити спільну мову між розробниками. Отже, коли я бачу посередника, я особисто розумію кілька елементів, які намагаються обмінюватися даними через одного брокера / хаба, щоб зменшити комунікаційний шум (або сприяти SRP), і кожен об’єкт однаково важливий з точки зору здатності сигналізувати про зміну стану. Наприклад, подумайте про кілька літаків, що підходять до аеропорту. Кожен повинен спілкуватися через пілон (посередник), а не спілкуватися один з одним. (Подумайте про 1000 літаків, які взаємодіють між собою при посадці - це був би безлад)

Однак, коли я бачу спостерігача, це означає, що є деякі зміни стану, про які я міг би піклуватися, і я повинен зареєструватися / підписатися, щоб прослухати певні зміни стану. Існує центральний об’єкт, відповідальний за сигналізацію про зміни стану. Наприклад, якщо я дбаю про конкретний аеропорт на шляху від А до В, я можу зареєструватися в цьому аеропорту, щоб ловити деякі трансляції подій, наприклад, якщо порожня смуга чи щось подібне.

Сподіваюся, це зрозуміло.


5

@cdc відмінно пояснив різницю в намірах.

Я додаю трохи більше інформації зверху.

Observer : дозволяє повідомляти про подію в одному об’єкті різному набору об’єктів (екземпляри різних класів)

Посередник : Централізуйте спілкування між сукупністю об’єктів, створених з певного класу.

Структура шаблону посередника з dofactory :

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

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

Колега : Це абстрактний клас, який визначає події , які будуть повідомлятися між колегами

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 спочатку додається до мережі через подію реєстрації. Ніяких інших колег не повідомляти, оскільки Eagle перший.
  2. Коли Страус додається до мережі, Eagle отримує повідомлення: рядок 1 виводу відображається зараз.
  3. Коли Пінгвіна додають до мережі, І Орла, і Страуса повідомляють: Рядок 2 і Рядок 3 виводяться зараз.
  4. Коли Ігл покинув мережу через незареєстровану подію, і Страус, і Пінгвін отримали повідомлення. Рядок 4 і рядок 5 виводу відображаються зараз.

2

Як щодо цього пояснення? Технічно і спостерігач, і посередник однакові і використовуються для забезпечення розв’язаного способу комунікації компонентів, але використання різне.

Поки obeserver сповіщає передплачені компоненти про зміни стану (наприклад, створення нового запису db), mediator команди реєструють компоненти, щоб робити щось, що стосується потоку бізнес-логіки (надсилання електронного листа користувачеві для скидання пароля).

Спостерігач

  • Споживачі сповіщень несуть відповідальність передплатити, щоб отримувати сповіщення
  • Обробка повідомлень не є частиною потоку бізнесу

Посередник

  • Явна реєстрація потрібна для зв’язку "видавця" та "споживачів"
  • Обробка повідомлень є частиною конкретного потоку бізнесу
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.