中介者模式
首发于:2022-04-12
基本概念
中介者模式 (Mediator Pattern)又称调停模式,使得各对象不用显式地相互引用,将对象与对象之间紧密的耦合关系变得松散,从而可以独立地改变他们。核心是多个对象之间复杂交互的封装。
根据最少知识原则,一个对象应该尽量少地了解其他对象。如果对象之间耦合性太高,改动一个对象则会影响到很多其他对象,可维护性差。复杂的系统,对象之间的耦合关系会得更加复杂,中介者模式就是为了解决这个问题而诞生的。
现实生活中的例子
说到中介者模式很容易想到媒人的例子吧,我在前面的 代理模式 中也用到了媒人这个例子,这时中介者就相当于同事对象间的代理。所以这时就可以引入代理模式的概念,对同事对象相互访问的时候,起到访问控制、功能扩展等等功能。
再举个飞机与塔台的例子:
飞行器驾驶员们在靠近或离开空中管制区域时不会直接相互交流。但他们会与飞机跑道附近,塔台中的空管员通话。如果没有空管员,驾驶员就需要留意机场附近的所有飞机,并与数十位飞行员组成的委员会讨论降落顺序。那恐怕会让飞机坠毁的统计数据一飞冲天吧。塔台无需管制飞行全程,只需在航站区加强管控即可,因为该区域的决策参与者数量对于飞行员来说实在太多了。
应用场景
中介者模式适用多个对象间的关系确实已经紧密耦合,且导致扩展、维护产生了困难的场景,也就是当多个对象之间的引用关系变成了网状结构的时候,此时可以考虑使用引入中介者来把网状结构转化为星型结构。如下图所示:
但是,如果对象之间的关系耦合并不紧密,或者之间的关系本就一目了然,那么引入中介者模式就是多此一举、画蛇添足。
实际上,我们通常使用的 MVC/MVVM 框架,就含有中介者模式的思想,Controller/ViewModel 层作为中介者协调 View/Model 进行工作,减少 View/Model 之间的直接耦合依赖,从而做到视图层和数据层的最大分离。
优缺点
优点:
- 松散耦合,降低了同事对象(上面图中的 ABCDE就是同事对象)之间的相互依赖和耦合,不会像之前那样牵一发动全身;
- 将同事对象间的一对多关联转变为一对一的关联,符合最少知识原则,提高系统的灵活性,使得系统易于维护和扩展;
- 中介者在同事对象间起到了控制和协调的作用,因此可以结合代理模式那样,进行同事对象间的访问控制、功能扩展;
- 因为同事对象间不需要相互引用,因此也可以简化同事对象的设计和实现;
缺点:
- 逻辑过度集中化,当同事对象太多时,中介者的职责将很重,逻辑变得复杂而庞大,以至于难以维护。
当出现中介者可维护性变差的情况时,考虑是否在系统设计上不合理,从而简化系统设计,优化并重构,避免中介者出现职责过重的情况。
实现
要实现中介者模式需要先知道两个概念,这两个概念在前面其实也有提到过:
- Colleague: 同事对象,只知道中介者而不知道其他同事对象,通过中介者来与其他同事对象通信;
- Mediator: 中介者,负责与各同事对象的通信;
/* 男方 */
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()
// 输出: 小帅 觉得合适~ (李小美 已经满足要求)
// 输出: 张小帅家长 觉得合适~ (李小美 已经满足要求)
// 输出: 李小美 觉得合适~ (张小帅 已经满足要求)
// 输出: 李小美家长 觉得不合适! (张小帅 不能满足要求!)
可以看到,除了媒人之外,其他各个角色都是独立的,相互不知道对方的存在,对象间关系被解耦,我们甚至可以方便地添加新的对象。