中介者模式的英文翻译是 Mediator Design Pattern。在 GoF 中的《设计模式》一书中,它是这样定义的:

Mediator pattern defines a separate (mediator) object that encapsulates the interaction between a set of objects and the objects delegate their interaction to a mediator object instead of interacting with each other directly.

翻译成中文就是:中介者模式定义了一个单独的(中介)对象,来封装一组对象之间的交互。将这组对象之间的交互委派给与中介对象交互,来避免对象之间的直接交互。

实际上,中介者模式的设计思想跟中间层很像,通过引入中介这个中间层,将一组对象之间的交互关系(或者说依赖关系)从多对多(网状关系)转换为一对多(星状关系)。原来一个对象要跟 n 个对象交互,现在只需要跟一个中介对象交互,从而最小化对象之间的交互关系,降低了代码的复杂度,提高了代码的可读性和可维护性。

中介者模式的结构与实现

结构

  • 抽象中介者 (Mediator),抽象中介者角色定义统一的接口用于个同时角色之间的通信。
  • 具体中介者(Concrete Mediator)具体中介者角色通过协调各同事角色实现协作行为。为此它要知道并引用各个同事角色。
  • 抽象同事类(Colleague),主要负责约束同事对象的类型,并实现一些具体同事类之间的公共功能。
  • 具体同事类(Concrete Colleague)每个具体同事类只知道自己的行为,而不了解其他同事的情况,但他们都认识中介者对象。需要与其他同事对象交互时,就通知中介对象,中介对象会负责后续的交互。

代码示例

Mediator.php

<?php
namespace DesignPatterns\Mediator\example1;


abstract class Mediator
{
    //定义一个抽象的发送消息方法,得到同事对象和发送信息
    abstract public function Send(string $message, Colleague $colleague);
}

Colleague.php

<?php
namespace DesignPatterns\Mediator\example1;

abstract class Colleague
{
    protected $mediator;
    //构造方法,得到中介者对象
    public function __construct(Mediator $mediator)
    {
        $this->mediator = $mediator;
    }
}

ConcreteMediator.php

<?php
namespace DesignPatterns\Mediator\example1;

class ConcreteMediator extends Mediator
{
    private $Colleague1;
    private $Colleague2;

    //需要了解所有的具体同事对象
    /**
     * @param mixed $colleague1
     */
    public function setColleague1($colleague1): void
    {
        $this->Colleague1 = $colleague1;
    }

    /**
     * @param mixed $colleague2
     */
    public function setColleague2($colleague2): void
    {
        $this->Colleague2 = $colleague2;
    }
    //重写发送消息的方法,根据对象作出选择的判断,通知对象
    public function Send(string $message, Colleague $colleague)
    {
        if ($this->Colleague1 == $colleague){
            $this->Colleague2->Notify($message);
        }else{
            $this->Colleague1->Notify($message);
        }

    }

}

ConcreteColleague1.php

<?php
namespace DesignPatterns\Mediator\example1;


class ConcreteColleague1 extends Colleague
{
    public function __construct(Mediator $mediator)
    {
        parent::__construct($mediator);
    }

    public function Send(string $message)
    {
        $this->mediator->Send($message,$this);//发送消息时通常是中介者发送出去的
    }

    public function Notify(string $message)
    {
        echo '  同事1得到消息:'.$message."<br>";
    }

}

ConcreteColleague2.php

<?php
namespace DesignPatterns\Mediator\example1;


class ConcreteColleague2 extends Colleague
{
    public function __construct(Mediator $mediator)
    {
        parent::__construct($mediator);
    }

    public function Send(string $message)
    {
        $this->mediator->Send($message,$this);
    }

    public function Notify(string $message)
    {
        echo '  同事2得到消息:'.$message."<br>";
    }
}

Client.php

<?php
namespace DesignPatterns\Mediator\example1;
require dirname(__DIR__).'/../vendor/autoload.php';
class Client
{
    public function run()
    {
        $m = new ConcreteMediator();
        //让两个具体同事类认识中介者对象
        $c1 = new ConcreteColleague1($m);
        $c2 = new ConcreteColleague2($m);
        //让中介者认识各个具体同事类对象
        $m->setColleague1($c1);
        $m->setColleague2($c2);
        //具体同事类对象的发送信息都是通过中介者转发
        $c1->Send("吃过饭了吗?");
        $c2->Send("没有呢,你打算请客吗?");
    }
}
$worker = new Client();
$worker->run();

运行结果:

由于有了Mediator,使得ConcreteColleague1和ConcreteColleague2在发送消息和接收消息时其实是通过中介者来完成的,这就减少了它们之间的耦合度。

示例

中介者模式的思想在现实生活中也很常见,比如说交换机。没有交换机存在的时代,每个电话之间都需要电话线连接才能进行通话。如果一个台电话要和其它100台电话通话,那么它就必须要有100条电话线与其它100个电话连接。

后来为了解决这种麻烦,交换机出现了。每个电话只需连入交换机,通话时。只需构建一条电话-交换机-电话的链路,就可以进行通话。所以现在我们的电话理论上可以同世界上任何一台电话通话,但是只需一条电话线。当然现在用电话的人少了,但是手机呀,计算机网络的实现也是在传统通信网的设计上进行演进的。

其实交换机对应的就是中介者模式的中介者,而电话机就是中介者中的同事。下面,就让我们用代码来实现这个思想。
Colleague.php

<?php
namespace DesignPatterns\Mediator\example2;
/**
 * 抽象同事类-电话机
 * Class Colleague
 * @package DesignPatterns\Mediator\example2
 */
abstract class Colleague
{
    protected $mediator;    //用于存放中介者
    abstract public function sendMsg($num,$msg);
    abstract public function receiveMsg($msg);
    /**
     * 设置中介者对象
     * @param mixed $mediator
     */
    final public function setMediator($mediator): void
    {
        $this->mediator = $mediator;
    }
}

PhoneColleague.php

<?php
namespace DesignPatterns\Mediator\example2;
//具体同事--固话
class PhoneColleague extends Colleague
{
    public function sendMsg($num, $msg)
    {
        echo '固话--发出声音:'.$msg."<br>";
        $this->mediator->operation($num,$msg);
    }
    public function receiveMsg($msg)
    {
        echo '固话--接收声音:'.$msg."<br>";
    }

}

Colleague.php

<?php
namespace DesignPatterns\Mediator\example2;
//具体同事--手机
class TelephoneColleague extends Colleague
{
    public function sendMsg($num, $msg)
    {
        echo '手机--发出声音:'.$msg."<br>";
        $this->mediator->operation($num,$msg);
    }
    public function receiveMsg($msg)
    {
        echo '手机响铃-------<br>';
        echo '手机--接收声音:'.$msg."<br>";
    }
}

Mediator.php

<?php
namespace DesignPatterns\Mediator\example2;
/**
 * 交换机 中介者角色
 * 当只需要一个具体中介者时,Mediator 和 ConcreteMediator可以合二为一
 * Class Mediator
 * @package DesignPatterns\Mediator\example2
 */
class Mediator
{
    protected $colleague =[];
    //交换机业务处理
    public function operation($num, $message)
    {
        if (!array_key_exists($num,$this->colleague)) {
            echo "交换机内没有此号码信息,无法通话".PHP_EOL;
        }else{
            $this->colleague[$num]->receiveMsg($message);
        }
    }
    //注册号码
    public function register($num,Colleague $colleague)
    {
        if (!in_array($colleague,$this->colleague)) {
            $this->colleague[$num] = $colleague;
        }
        $colleague->setMediator($this);
    }


}

Client.php

<?php
namespace DesignPatterns\Mediator\example2;
require dirname(__DIR__).'/../vendor/autoload.php';
/**
 * Class Client
 * @package DesignPatterns\Mediator\example2
 */
class Client
{
    public function run()
    {
        //实例化固话
        $phone = new PhoneColleague();
        $telePhone = new TelephoneColleague();
        $mediator = new Mediator();
        //注册号码
        $mediator->register('6668688',$phone);
        $mediator->register('15612341234',$telePhone);

        //通话
        $phone->sendMsg('15612341234','hello world');
        $telePhone->sendMsg('6668688','请讲普通话');
        $telePhone->sendMsg('6668688','请讲普通话');
    }
}

$worker = new Client();
$worker->run();

运行结果:

中介者模式的优缺点

中介者模式的优点首先是Mediator的出现减少了各个colleague的耦合,使得可以独立的改变和复用各个colleague类和mediator使得任何colleague的改变都不会影响到其他colleague而只与mediator发生变化。其次,由于把对象协作进行了抽象,把中介作为一个独立的概念并将其封装在一个对象中,这样关注的对象从对象各自本身的行为转移到他们之间的交互上来,也就是站在一个更宏观的角度去看待系统。

中介者模式的缺点,首先具体中介者类ConcreteMediator可能会因为ConcreteColleague的越来越多耳边的非常复杂反而不容易维护了。由于ConcreteMediator控制了集中化,于是就把交互复杂性变为了中介者的复杂性,这就使得中介者会变得比任何一个ConcreteColleague都复杂化。

中介模式 VS 观察者模式

我们前面提到,中介模式也是为了解耦对象之间的交互,所有的参与者都只与中介进行交互。而观察者模式中的消息队列,就有点类似中介模式中的“中介”,观察者模式的中观察者和被观察者,就有点类似中介模式中的“参与者”。那问题来了:中介模式和观察者模式的区别在哪里呢?什么时候选择使用中介模式?什么时候选择使用观察者模式呢?

在观察者模式中,尽管一个参与者既可以是观察者,同时也可以是被观察者,但是,大部分情况下,交互关系往往都是单向的,一个参与者要么是观察者,要么是被观察者,不会兼具两种身份。也就是说,在观察者模式的应用场景中,参与者之间的交互关系比较有条理。

而中介模式正好相反。只有当参与者之间的交互关系错综复杂,维护成本很高的时候,我们才考虑使用中介模式。毕竟,中介模式的应用会带来一定的副作用,前面也讲到,它有可能会产生大而复杂的上帝类。除此之外,如果一个参与者状态的改变,其他参与者执行的操作有一定先后顺序的要求,这个时候,中介模式就可以利用中介类,通过先后调用不同参与者的方法,来实现顺序的控制,而观察者模式是无法实现这样的顺序要求的。

总结

中介模式的设计思想跟中间层很像,通过引入中介这个中间层,将一组对象之间的交互关系(或者依赖关系)从多对多(网状关系)转换为一对多(星状关系)。原来一个对象要跟 n 个对象交互,现在只需要跟一个中介对象交互,从而最小化对象之间的交互关系,降低了代码的复杂度,提高了代码的可读性和可维护性。

观察者模式和中介模式都是为了实现参与者之间的解耦,简化交互关系。两者的不同在于应用场景上。在观察者模式的应用场景中,参与者之间的交互比较有条理,一般都是单向的,一个参与者只有一个身份,要么是观察者,要么是被观察者。而在中介模式的应用场景中,参与者之间的交互关系错综复杂,既可以是消息的发送者、也可以同时是消息的接收者。

中介者模式很容易在系统中应用,也很容易在系统中误用,当系统出现‘多对多’交互复杂的对象群时,不要急于使用中介者模式,而要先看看我们的系统在设计上是否合理。

参考资料: 设计模式之美-中介模式:什么时候用中介模式?什么时候用观察者模式?

github示例:https://github.com/yangpanyao/design-patterns/tree/master/Mediator

Last modification:July 23rd, 2020 at 05:00 pm