【设计模式系列】六、中介者模式

我们在之前的系列文章中,陆续介绍了:

今天继续给大家带来“中介者模式”的介绍。

中介者模式(Mediator),用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。

软件开发中,中介者是一个行为设计模式,通过提供一个统一的接口让系统的不同部分进行通信。

一般,如果系统有很多子模块需要直接沟通,都要创建一个中央控制点让其各模块通过该中央控制点进行交互。中介者模式可以让这些子模块不需要直接沟通,而达到进行解耦的目的。

打个比方,平时常见的机场交通控制系统,塔台就是中介者,它控制着飞机(子模块)的起飞和降落,因为所有的沟通都是从飞机向塔台汇报来完成的,而不是飞机之前相互沟通。中央控制系统就是该系统的关键,也就是软件设计中扮演的中介者角色。

实现

我们先用伪代码来理解一下:

// 如下代码是伪代码,请不要过分在意代码
// 这里app命名空间就相当于扮演中介者的角色
let app = app || {};
 
// 通过app中介者来进行Ajax请求
app.sendRequest = function ( options {
    return $.ajax($.extend({}, options);
}
 
// 请求URL以后,展示View
app.populateView = function( url, view ){
  $.when(app.sendRequest({url: url, method'GET'})
     .then(function(){
         //显示内容
     });
}
 
// 清空内容
app.resetView = function( view ){
   view.html('');
}

JavaScript 里,中介者非常常见,相当于观察者模式上的消息 Bus,只不过不像观察者那样通过调用 pub/sub 的形式来实现,而是通过中介者统一来管理,让我们在观察者的基础上来给出一个例子:

var mediator = (function ({
    // 订阅一个事件,并且提供一个事件触发以后的回调函数
    var subscribe = function (channel, fn{
        if (!mediator.channels[channel]) mediator.channels[channel] = [];
        mediator.channels[channel].push({ contextthiscallback: fn });
        return this;
    },

    // 广播事件
    publish = function (channel{
        if (!mediator.channels[channel]) return false;
        var args = Array.prototype.slice.call(arguments1);
        for (var i = 0, l = mediator.channels[channel].length; i < l; i++) {
            var subscription = mediator.channels[channel][i];
            subscription.callback.apply(subscription.context, args);
        }
        return this;
    };

    return {
        channels: {},
        publish: publish,
        subscribe: subscribe,
        installTofunction (obj{
            obj.subscribe = subscribe;
            obj.publish = publish;
        }
    };

} ());

调用代码,相对就简单了:

(function (Mediator{

    function initialize({

        // 默认值
        mediator.name = "dudu";

        // 订阅一个事件nameChange
        // 回调函数显示修改前后的信息
        mediator.subscribe('nameChange'function (arg{
            console.log(this.name);
            this.name = arg;
            console.log(this.name);
        });
    }

    function updateName({
        // 广播触发事件,参数为新数据
        mediator.publish('nameChange''tom'); // dudu, tom
    }

    initialize(); // 初始化
    updateName(); // 调用

})(mediator);

应用

如果多个用户之间存在复杂的通信和耦合的情况时候,则可以考虑使用中介者模式。

/**
 * 1. 抽象出结构
 */



// 抽象出中介者
abstract class Mediator {
  colleagues: Colleague[];

  abstract register(colleague: Colleague): void;
  abstract relay(colleague: Colleague): void;
}

// 抽象需要交互的对象
abstract class Colleague {
  mediator: Mediator;

  setMediator(mediator: Mediator) {
    this.mediator = mediator;
  }

  abstract receive(): void;
  abstract send(): void;
}

/**
 * 2. 具现
 */


// 中介者,内部维护一个 List ,保存了依赖这个中介者的所有对象
class ConcreteMediator extends Mediator {
  colleagues: Colleague[] = [];

  register(colleague: Colleague): void {
    const flag = this.colleagues.some((item) => item === colleague);

    if (!flag) this.colleagues.push(colleague);
  }
  relay(colleague: Colleague): void {
    this.colleagues.forEach((item) => {
      if (item !== colleague) item.receive();
    })
  }
}

class ConcreteColleague extends Colleague {
  name: string;
  constructor(name: string) {
    super();
    this.name = name;
  }
  receive(): void {
    console.log(`${this.name}接收的信号`);
  }
  send(): void {
    console.log(`${this.name}发出的信号`);
    this.mediator.relay(this);
  }
}

// 1. 初始化中介者
const mediator  = new ConcreteMediator();

// 2. 创建关联对象
const c1 = new ConcreteColleague("c1");
const c2 = new ConcreteColleague("c2");
const c3 = new ConcreteColleague("c3");

// 3. 将两者关联起来
mediator.register(c1);
mediator.register(c2);
mediator.register(c3);

c1.setMediator(mediator);
c2.setMediator(mediator);
c3.setMediator(mediator);

// 4. 测试
c1.send();
// c1发出的信号
// c2接收的信号
// c3接收的信号


c2.send();
// c2发出的信号
// c1接收的信号
// c3接收的信号

总结说明

中介者模式一般应用于一组对象已定义良好但是以复杂的方式进行通信的场合,一般情况下,中介者模式很容易在系统中使用,但也容易在系统里误用,当系统出现了多对多交互复杂的对象群时,先不要急于使用中介者模式,而是要思考一下是不是系统设计有问题。

另外,由于中介者模式把交互复杂性变成了中介者本身的复杂性,所以说中介者对象会比其它任何对象都复杂。

最后

《前端面试题宝典》经过近一年的迭代,现已推出 小程序 和 电脑版刷题网站 (https://fe.ecool.fun/),欢迎大家使用~

同时,我们还推出了面试辅导的增值服务,可以为大家提供 “简历指导” 和 “模拟面试” 服务,感兴趣的同学可以联系小助手(微信号:interview-fe)进行报名。