模式补全
这一节按 八面剖析以理解一个概念 / ljg-learn 的思路整理为 Markdown 版本:先定锚,再用八个方向切开概念,最后压缩成公式、例子、类图和检验题。
定锚
中介者模式(行为型)的通行定义:用一个中介对象封装多个对象之间的交互,减少对象之间的网状依赖。
常见误解:中介者不是万能管理器;它只应该协调交互,不应该吞掉所有业务职责。
核心词素:Mediator 是调停者。它把多对多沟通变成对象到中介的一对多沟通。
八刀
历史
它常见于对话框控件、聊天室、模块事件协调。GoF 用它降低对象协作复杂度。
辩证
反面是对象互相引用成网。更高理解是:复杂协作需要一个显式协议中心。
现象
聊天室里用户不逐个给所有人发消息,而是发给房间,房间再广播。
语言
Mediator 是调停者。它把多对多沟通变成对象到中介的一对多沟通。
形式
Colleague → Mediator.notify(event)。若所有逻辑都塞进中介者,它会变成中心化泥球。
存在
它让关系本身成为对象,系统不再被隐形连线缠住。
美感
它美在广场:所有声音先来到中心,再被有序分发。
元反思
中介隐喻容易鼓励中心化。换成“交通枢纽”隐喻,会提醒我们控制流量和分区。
内观
我是协作中心。组件不必彼此认识,只把事件告诉我。我安排谁该回应。
八刀共同指向的深层结构:中介者模式不是为了“炫技”,而是在某个变化点上建立边界,让稳定部分继续稳定,让变化部分有自己的位置。
压缩
公式:中介者 = 同事对象 + 协调中心 + 交互解耦
一句话:中介者把对象之间的网状沟通收敛为通过中心协议的沟通。
结构图:
A/B/C -> Mediator -> A/B/C动机/意图
当多个对象彼此直接引用、互相通知时,关系会从简单依赖变成网状依赖。中介者模式的意图是把对象之间的复杂交互集中到一个协调者中,让同事对象只和中介者沟通。
结构/角色
Mediator:中介者接口,定义同事对象之间的通信协议。ConcreteMediator:具体中介者,保存同事对象引用并协调交互。Colleague:同事抽象,持有中介者引用。ConcreteColleague:具体同事,发生事件时通知中介者,由中介者转发或协调其他对象。Client:客户端组装中介者和同事对象。
典型 UML
classDiagram class Mediator { <<interface>> +notify(sender: Colleague, event: string): void } class ConcreteMediator class Colleague { -mediator: Mediator } class ConcreteColleagueA class ConcreteColleagueB Mediator <|.. ConcreteMediator Colleague <|-- ConcreteColleagueA Colleague <|-- ConcreteColleagueB ConcreteMediator o-- ConcreteColleagueA ConcreteMediator o-- ConcreteColleagueB Colleague --> Mediator
使用场景
- 多个组件之间交互复杂,直接引用导致难以维护。
- UI 表单、聊天室、工作流节点等需要中心协调。
- 希望复用同事对象,但交互规则经常变化。
- 对象之间的通信规则比对象自身行为更重要。
正例:TypeScript
这个例子里,ChatGroup 是中介者,所有会员都不再彼此直接通信,而是统一把消息交给聊天室。聊天室负责过滤不雅字符,并限制钻石会员发送图片时的大小。
abstract class Member {
constructor(
public readonly name: string,
protected chatGroup: ChatGroup,
) {}
receive(from: string, message: string) {
console.log(`${this.name} receives from ${from}: ${message}`)
}
}
class CommonMember extends Member {
sendText(to: Member, message: string) {
this.chatGroup.sendText(this, to, message)
}
}
class DiamondMember extends Member {
sendText(to: Member, message: string) {
this.chatGroup.sendText(this, to, message)
}
sendImage(to: Member, imageName: string, sizeKb: number) {
this.chatGroup.sendImage(this, to, imageName, sizeKb)
}
}
class ChatGroup {
private readonly bannedWords = ["bad", "ugly"]
private readonly maxImageSizeKb = 2048
private filterMessage(message: string) {
return this.bannedWords.reduce(
(result, word) => result.replaceAll(word, "***"),
message,
)
}
sendText(from: Member, to: Member, message: string) {
const filtered = this.filterMessage(message)
to.receive(from.name, filtered)
}
sendImage(
from: DiamondMember,
to: Member,
imageName: string,
sizeKb: number,
) {
if (sizeKb > this.maxImageSizeKb) {
console.log(`image ${imageName} is too large to send`)
return
}
to.receive(from.name, `[image:${imageName}, size=${sizeKb}KB]`)
}
}
const chatGroup = new ChatGroup()
const alice = new CommonMember("Alice", chatGroup)
const bob = new CommonMember("Bob", chatGroup)
const diana = new DiamondMember("Diana", chatGroup)
alice.sendText(bob, "hello, bad word here")
diana.sendImage(alice, "design.png", 1024)正例:UML 类图
classDiagram class ChatGroup { -bannedWords: string[] -maxImageSizeKb: number +sendText(from: Member, to: Member, message: string): void +sendImage(from: DiamondMember, to: Member, imageName: string, sizeKb: number): void -filterMessage(message: string): string } class Member { <<abstract>> +name: string +receive(from: string, message: string): void } class CommonMember { +sendText(to: Member, message: string): void } class DiamondMember { +sendText(to: Member, message: string): void +sendImage(to: Member, imageName: string, sizeKb: number): void } CommonMember --|> Member DiamondMember --|> Member Member --> ChatGroup ChatGroup ..> Member
反例:TypeScript
反例里没有中介者,会员对象彼此直接引用。这样文本过滤、图片大小校验这些规则会散落在各个会员类里,耦合很快变成一张网。
class CommonMember {
private contacts: Array<CommonMember | DiamondMember> = []
constructor(public readonly name: string) {}
addContact(member: CommonMember | DiamondMember) {
this.contacts.push(member)
}
sendText(to: CommonMember | DiamondMember, message: string) {
const filtered = message.replaceAll("bad", "***")
to.receive(this.name, filtered)
}
receive(from: string, message: string) {
console.log(`${this.name} receives from ${from}: ${message}`)
}
}
class DiamondMember {
private contacts: Array<CommonMember | DiamondMember> = []
constructor(public readonly name: string) {}
addContact(member: CommonMember | DiamondMember) {
this.contacts.push(member)
}
sendText(to: CommonMember | DiamondMember, message: string) {
const filtered = message.replaceAll("bad", "***")
to.receive(this.name, filtered)
}
sendImage(
to: CommonMember | DiamondMember,
imageName: string,
sizeKb: number,
) {
if (sizeKb > 2048) {
console.log(`image ${imageName} is too large to send`)
return
}
to.receive(this.name, `[image:${imageName}, size=${sizeKb}KB]`)
}
receive(from: string, message: string) {
console.log(`${this.name} receives from ${from}: ${message}`)
}
}反例:UML 类图
classDiagram class CommonMember { +sendText(to, message): void +receive(from, message): void } class DiamondMember { +sendText(to, message): void +sendImage(to, imageName, sizeKb): void +receive(from, message): void } CommonMember --> CommonMember CommonMember --> DiamondMember DiamondMember --> CommonMember DiamondMember --> DiamondMember
案例
某论坛系统欲增加一个虚拟聊天室,允许论坛会员通过该聊天室进行信息交流,普通会员(CommonMember)可以给其他会员发送文本信息,钻石会员(DiamondMember)既可以给其他会员发送文本信息,还可以发送图片信息。该聊天室可以对不雅字符进行过滤;还可以对发送的图片大小进行控制。用中介者模式设计该虚拟聊天室。
掌握检验
- 中介者模式解决的是对象之间哪种依赖形态?
- 中介者和观察者都能分发消息,它们关注点有什么不同?
- 如何避免中介者变成上帝对象?