中介者模式与观察者模式的面向对象设计模式比较

118

我一直在阅读四人帮,以解决我的一些问题,并遇到了中介者模式。

我之前在我的项目中使用了观察者来制作一些GUI应用程序。我有点困惑,因为我没有发现两者之间有很大的区别。我浏览了一下,但没有找到任何明确区分两者的好例子。

能否有人通过一些清晰区分两者的好例子来帮助我区分它们?


6
我的请求将这个问题迁移到 Programmers.StackExchange 被拒绝了,但是我在那里发布了一个类似的帖子,因为我对答案很感兴趣。您可能会发现其中一些答案很有趣。 :) - Rachel
对于 JavaScript 的示例,您可以查看我对类似问题的回答 - Alex Pakka
原始的GoF书籍在实现部分的第8点中提到了这一点,通过给出一个使用中介者模式的观察者模式的ChangeManager示例来说明。请参见:https://paginas.fe.up.pt/~aaguiar/as/gof/hires/pat5g.htm#samplecode - robi-y
8个回答

122

观察者模式: 定义对象之间的一对多依赖关系,以使得当一个对象改变状态时,所有依赖于它的对象都会被自动通知和更新。

中介者模式: 定义一个对象来封装一组对象之间的交互方式。中介者通过保持对象不直接相互引用来促进松散耦合,并且让您可以独立地改变它们的交互方式。

来源:dofactory

示例:

观察者模式: 类A可以注册零个或多个类型为O的观察者。当A中的某些内容发生变化时,它会通知所有观察者。

中介者模式: 您拥有一些类X的实例(甚至可能是几种不同类型:X、Y和Z),它们希望相互通信(但您不希望每一个类都有显式的引用关系),因此您创建了一个中介者类M。每个X类实例都有一个对共享M实例的引用,通过它可以与其他X类实例(或X、Y和Z)进行通信。


1
观察者模式的解释似乎更接近命令模式而不是观察者模式。 - Aun

49
在最初提出观察者和中介者术语的书籍《设计模式,可重用面向对象软件的基础》中,它说中介者模式可以通过使用观察者模式来实现。然而,它也可以通过让同事(大致相当于观察者模式的主题)引用中介者类或中介者接口来实现。
有许多情况下你会想使用观察者模式,关键是一个对象不应该知道其他对象正在观察它的状态。
中介者模式更加具体,它避免了类直接通信,而是通过一个中介者进行通信。这有助于单一职责原则,通过允许通信被卸载到只处理通信的类中。
经典的中介者模式示例是在GUI中,其中天真的方法可能会导致按钮单击事件上的代码说“如果Foo面板被禁用且Bar面板有一个标签说“请输入日期”,则不要调用服务器,否则继续”,而使用中介者模式,则可以说“我只是一个按钮,并且不知道Foo面板和Bar面板上标签的情况,所以我将询问我的中介者是否现在可以调用服务器。”
或者,如果使用观察者模式来实现中介者,则按钮会说“嘿,观察者们(其中包括中介者),我的状态改变了(有人点击了我)。如果你关心的话,就对此做些事情。”在我的例子中,这可能比直接引用中介者更少意义,但在许多情况下,使用观察者模式来实现中介者将是有意义的,观察者和中介者之间的区别更多地是一种意图上的差异而不是代码本身的差异。

我在寻找这个词“单一职责原则”。 - stdout

45

观察者模式

1. 未使用观察者模式

  • 客户端1:嘿主题,你什么时候改变?

  • 客户端2:你什么时候改变了主题?我没有注意到!

  • 客户端3:我知道主题已经改变了。

2. 使用观察者模式

  • 客户端们保持沉默。

  • 过了一段时间...

  • 主题:亲爱的客户端们,我已经改变了!


中介者模式

1. 未使用中介者模式

  • 客户端1:嘿出租车1,带我去某个地方。

  • 客户端2:嘿出租车1,带我去某个地方。

  • 客户端1:嘿出租车2,带我去某个地方。

  • 客户端2:嘿出租车2,带我去某个地方。

2. 使用中介者模式

  • 客户端1:嘿出租车中心,请给我安排一辆出租车

  • 客户端2:嘿出租车中心,请给我安排一辆出租车


3
你的调停者示例是工厂模式,而不是调停者模式。 - Mammad Karimi
4
@Pmpr 这是中介者设计模式。TaxiCenter不会创建出租车,它通过某种方式使出租车可用(可能每辆出租车都等待,直到TaxiCenter说轮到你了)。 - Siva R
3
出租车中心并不会“创建”出租车,它是将出租车分配给客户。 - Peyman Mohamadpour

16

这些模式用于不同的情况:

当您有两个具有某些依赖关系的子系统,并且其中一个子系统需要进行更改时,中介者模式就会被使用。由于您可能不想更改依赖于其他子系统的系统,因此您可能希望引入一个中介者来解耦它们之间的依赖关系。这样,当其中一个子系统发生更改时,您只需更新中介者。

观察者模式则用于当一个类想要允许其他类注册自己并在事件发生时接收通知时使用,例如ButtonListener等。

这两种模式都允许较少的耦合,但是它们是非常不同的。


15

让我们来举个例子:假设你想构建两个应用程序:

  1. 聊天应用。
  2. 紧急救护操作员应用。

中介者模式

在构建聊天应用时,您将选择 中介者 设计模式。

  • 参与聊天的人们可能随时加入和离开,因此在两个聊天者之间保持直接引用是没有意义的。
  • 我们仍然需要促进两个人之间的沟通并允许他们进行聊天。

为什么会选择 中介者?只需看一下其定义即可:

使用中介者模式,对象之间的通信被封装在一个中介者对象内。对象不再直接相互通信,而是通过中介者进行通信。这减少了相互通信的对象之间的依赖关系,从而减少了耦合。

这种魔法是如何运作的呢?首先,我们将创建聊天中介者并使人物对象注册到它上面,因此它将具有每个单个人物对象的双向连接(人物可以使用聊天中介者发送消息,因为它可以访问它,并且聊天中介者将访问人物对象的接收方法,因为它也可以访问它)。

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(ronan, 'Hello there, nice to meet you');
ronan.send(colton, 'Nice to meet you to');

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

观察者模式

在构建911呼叫应用程序时,您将选择观察者设计模式。

  • 每个救护车观察者对象都希望在紧急情况下得到通知,以便他可以前往地址并提供帮助。
  • 应急运营商可观察的引用每个救护车观察者的对象,并在需要帮助(或生成事件)时通知它们。

为什么我们会更喜欢观察者?只需看一下其定义:

一个名为主题的对象维护其依赖项列表,称为观察者,并通过调用它们的方法之一自动通知它们任何状态更改。

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. 聊天室的中介者具有双向通信,可以在人员对象之间进行交流(发送和接收),而操作符可观察者仅具有单向通信(告诉救护车观察者驾驶并完成任务)。
  2. 聊天室的中介者可以使人员对象相互交互(即使不是直接交流),而救护车观察者仅注册为操作符可观察者的事件。
  3. 每个人对象都有对聊天室中介者的引用,聊天室中介者也保留对每个人的引用。而救护车观察者不保留对操作符可观察者的引用,只有操作符可观察者保留对每个救护车观察者的引用。

8
最后一点很有帮助。调停者和观察者都能达到同样的目标,但是调停者可以实现双向通信,而观察者只能单向通信。 - kiwicomb123
没错,很高兴能帮到你。 - Shahar Shokrani
当我读到标题时,实际上我完全相反了。救护车呼叫中心是一个调解者 - 接收呼叫然后派遣救护车、警察和消防员。 每个无线电台上的救护车都是观察者 - 他们等待与他们相关的无线电事件。 在聊天中:服务器是调解者,每个客户端都是观察者。你觉得这样说对吗? - Maks
嘿,@Maks,你的评论给了我写一篇基于我的回答的文章的动力:https://shokrano.medium.com/mediator-vs-observer-19e3b21ef31c,谢谢! - Shahar Shokrani
感谢您花时间进行说明,我可以问一个问题吗?在聊天示例中,colton是要发送给ronan的吗?即使如此,colton也必须认识ronan才能执行colton.send(ronan,“你好,很高兴见到你”); 这意味着发送者取决于接收者,对吗? - Social Developer
显示剩余2条评论

8
虽然两者都用于有组织地告知状态变化,但在结构和语义上略有不同。 观察者用于从对象本身广播特定对象的状态更改。 因此,更改发生在负责发出信号的中心对象中。 然而,在中介者中,状态更改可以发生在任何对象中,但是它是从中介者广播的。 因此,流程上存在差异。 但是,我认为这并不影响我们的代码行为。 我们可以使用其中之一来实现相同的行为。 另一方面,这种差异可能会对代码的概念理解产生一些影响。
请注意,使用设计模式的主要目的是在开发人员之间创建一个共同的语言。 因此,当我看到中介者时,我个人理解多个元素正在尝试通过单个代理/中心进行通信,以减少通信噪声(或促进SRP),并且每个对象在具有发出状态更改的能力方面同等重要。 例如,考虑多架飞机接近机场。 每个人都应该通过桩(中介者)进行通信,而不是彼此通信。 (想象一下1000架飞机着陆时互相通信 - 那将是一团糟)
然而,当我看到观察者时,这意味着可能会关心某些状态更改,并且应该注册/订阅以侦听特定的状态更改。 有一个负责发出状态更改的中心对象。 例如,如果我在从A到B的路上关心特定机场,则可以注册到该机场以捕获广播的一些事件,例如是否有空跑道之类的事件。
希望这很清楚。

5

@cdc解释了意图的差异,非常出色。

我会在此基础上添加一些信息。

观察者模式:使一个对象中的事件通知到不同类的实例中。

中介者模式:集中管理从特定类创建的一组对象之间的通信。

来自 dofactory 的中介者模式结构:

进入图片描述

中介者:定义 Colleagues 之间通信的接口。

Colleague:是一个抽象类,其定义了要在 Colleagues 之间通信的事件。

ConcreteMediator:通过协调 Colleague 对象实现协作行为,并维护其 colleagues。

ConcreteColleague:实现通过 Mediator 接收到的通知操作,该操作由其他 Colleague 生成。

一个真实的例子:

您正在维护一个网状拓扑结构的计算机网络。如果添加了新计算机或删除了现有计算机,则该网络中的所有其他计算机都应该知道这两个事件。

让我们看看中介者模式如何适用于此。

代码片段:

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. Ostrich加入网络时,Eagle会收到通知:现在呈现输出的第一行。
  3. Penguin加入网络时,EagleOstrich都已经收到了通知:现在呈现输出的第二行和第三行。
  4. Eagle通过注销事件离开网络时,OstrichPenguin都已经收到了通知。现在呈现输出的第四行和第五行。

2

这是一个关于Observer和Mediator的解释。从技术上讲,它们都可以用于提供组件通信的解耦方式,但使用方式不同。

observer 通知已经 订阅 的组件有状态变化时(例如创建新的数据库记录),mediator 命令已经 注册 的组件执行与业务逻辑流程相关的操作(例如向用户发送密码重置邮件)。

Observer

  • 通知消费者负责订阅以接收通知
  • 通知处理不属于业务流程的一部分

Mediator

  • 必须显式注册才能连接“发布者”和“消费者”
  • 通知处理是特定业务流程的一部分

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接