跳转到内容

中介者模式

首发于:2022-04-12

基本概念

中介者模式 (Mediator Pattern)又称调停模式,使得各对象不用显式地相互引用,将对象与对象之间紧密的耦合关系变得松散,从而可以独立地改变他们。核心是多个对象之间复杂交互的封装

根据最少知识原则,一个对象应该尽量少地了解其他对象。如果对象之间耦合性太高,改动一个对象则会影响到很多其他对象,可维护性差。复杂的系统,对象之间的耦合关系会得更加复杂,中介者模式就是为了解决这个问题而诞生的。

现实生活中的例子

说到中介者模式很容易想到媒人的例子吧,我在前面的 代理模式 中也用到了媒人这个例子,这时中介者就相当于同事对象间的代理。所以这时就可以引入代理模式的概念,对同事对象相互访问的时候,起到访问控制、功能扩展等等功能。

再举个飞机与塔台的例子:

飞行器驾驶员们在靠近或离开空中管制区域时不会直接相互交流。但他们会与飞机跑道附近,塔台中的空管员通话。如果没有空管员,驾驶员就需要留意机场附近的所有飞机,并与数十位飞行员组成的委员会讨论降落顺序。那恐怕会让飞机坠毁的统计数据一飞冲天吧。塔台无需管制飞行全程,只需在航站区加强管控即可,因为该区域的决策参与者数量对于飞行员来说实在太多了。

应用场景

中介者模式适用多个对象间的关系确实已经紧密耦合,且导致扩展、维护产生了困难的场景,也就是当多个对象之间的引用关系变成了网状结构的时候,此时可以考虑使用引入中介者来把网状结构转化为星型结构。如下图所示:

中介者模式.png

但是,如果对象之间的关系耦合并不紧密,或者之间的关系本就一目了然,那么引入中介者模式就是多此一举、画蛇添足。

实际上,我们通常使用的 MVC/MVVM 框架,就含有中介者模式的思想,Controller/ViewModel 层作为中介者协调 View/Model 进行工作,减少 View/Model 之间的直接耦合依赖,从而做到视图层和数据层的最大分离。

优缺点

优点:

  1. 松散耦合,降低了同事对象(上面图中的 ABCDE就是同事对象)之间的相互依赖和耦合,不会像之前那样牵一发动全身;
  2. 将同事对象间的一对多关联转变为一对一的关联,符合最少知识原则,提高系统的灵活性,使得系统易于维护和扩展;
  3. 中介者在同事对象间起到了控制和协调的作用,因此可以结合代理模式那样,进行同事对象间的访问控制、功能扩展
  4. 因为同事对象间不需要相互引用,因此也可以简化同事对象的设计和实现

缺点:

  1. 逻辑过度集中化,当同事对象太多时,中介者的职责将很重,逻辑变得复杂而庞大,以至于难以维护。

当出现中介者可维护性变差的情况时,考虑是否在系统设计上不合理,从而简化系统设计,优化并重构,避免中介者出现职责过重的情况。

实现

要实现中介者模式需要先知道两个概念,这两个概念在前面其实也有提到过:

  1. Colleague: 同事对象,只知道中介者而不知道其他同事对象,通过中介者来与其他同事对象通信;
  2. Mediator: 中介者,负责与各同事对象的通信;
js
/* 男方 */
const ZhangXiaoShuai = {
  name: '张小帅',
  family: '张小帅家',
  info: { age: 25, height: 171, salary: 5000 },
  target: { age: [23, 27] }
}

/* 男方家长 */
const ZhangXiaoShuaiParent = {
  name: '张小帅家长',
  family: '张小帅家',
  info: null,
  target: { height: [160, 167] }
}

/* 女方 */
const LiXiaoMei = {
  name: '李小美',
  family: '李小美家',
  info: { age: 23, height: 160 },
  target: { age: [25, 27] }
}

/* 女方家长 */
const LiXiaoMeiParent = {
  name: '李小美家长',
  family: '李小美家',
  info: null,
  target: { salary: [10000, 20000] }
}

/* 媒人 */
const MatchMaker = {
  matchBook: {},		// 媒人的花名册

  /* 注册各方 */
  registPersons(...personList) {
    personList.forEach(person => {
      if (this.matchBook[person.family]) {
        this.matchBook[person.family].push(person)
      } else this.matchBook[person.family] = [person]
    })
  },

  /* 检查对方家庭的孩子对象是否满足要求 */
  checkAllPurpose() {
    Object.keys(this.matchBook)    // 遍历名册中所有家庭
      .forEach((familyName, idx, matchList) =>
        matchList
          .filter(match => match !== familyName)  // 对于其中一个家庭,过滤出名册中其他的家庭
          .forEach(enemyFamily => this.matchBook[enemyFamily]  // 遍历该家庭中注册到名册上的所有成员
            .forEach(enemy => this.matchBook[familyName]
              .forEach(person =>             // 逐项比较自己的条件和他们的要求
                enemy.info && this.checkPurpose(person, enemy)
              )
            ))
      )
  },

  /* 检查对方是否满足自己的要求,并发信息 */
  checkPurpose(person, enemy) {
    const result = Object.keys(person.target)    // 是否满足自己的要求
      .every(key => {
        const [low, high] = person.target[key]
        return low <= enemy.info[key] && enemy.info[key] <= high
      })
    this.receiveResult(result, person, enemy)    // 通知对方
  },

  /* 通知对方信息 */
  receiveResult(result, person, enemy) {
    result
      ? console.log(`${person.name} 觉得合适~ \t(${enemy.name} 已经满足要求)`)
      : console.log(`${person.name} 觉得不合适! \t(${enemy.name} 不能满足要求!)`)
  }
}


/* 注册 */
MatchMaker.registPersons(ZhangXiaoShuai, ZhangXiaoShuaiParent, LiXiaoMei, LiXiaoMeiParent)

MatchMaker.checkAllPurpose()

// 输出: 小帅 觉得合适~ 	    (李小美 已经满足要求)
// 输出: 张小帅家长 觉得合适~ 	(李小美 已经满足要求)
// 输出: 李小美 觉得合适~ 	    (张小帅 已经满足要求)
// 输出: 李小美家长 觉得不合适! 	(张小帅 不能满足要求!)

可以看到,除了媒人之外,其他各个角色都是独立的,相互不知道对方的存在,对象间关系被解耦,我们甚至可以方便地添加新的对象。

京ICP备18043750号