Go语言设计模式:中介者模式详解
文章目录
- 一、中介者模式概述
 - 1.1 中介者模式介绍
 - 1.2 模式核心概念
 - 1.3 UML 类图
 - 1.4 优缺点分析
 - 1.5 与观察者模式的区别
 
- 二、Go语言实现中介者模式
 - 2.1 步骤 1: 定义中介者和同事接口
 - 2.2 步骤 2: 实现具体同事
 - 2.3 步骤 3: 实现具体中介者
 - 2.4 步骤 4: 客户端代码与演示
 - 2.5 完整代码
 - 2.6 执行结果和分析
 
一、中介者模式概述
1.1 中介者模式介绍
中介者模式是一种行为设计模式,它让你能减少一系列对象之间混乱的依赖关系。该模式会限制对象之间的直接交互,迫使它们通过一个中介者对象进行合作。
 想象一下机场的塔台。飞行员不会直接与其他飞行员沟通来决定谁可以降落,而是所有人都向塔台(中介者)报告,由塔台来协调降落顺序。这样,飞行员(同事对象)之间就解耦了,塔台集中控制了所有的通信逻辑。
1.2 模式核心概念
中介者模式主要包含四个核心角色:
- Mediator (中介者): 
- 定义一个接口,用于与各同事对象(Colleague)之间通信。
 
 - ConcreteMediator (具体中介者): 
- 实现中介者接口,协调各个同事对象之间的交互。
 - 它知道并维护所有的同事对象,并负责它们之间的逻辑协调。
 
 - Colleague (同事): 
- 定义一个接口,让同事对象知道其中介者是谁。通常,每个同事对象都持有一个对中介者的引用。
 
 - ConcreteColleague (具体同事): 
- 实现同事接口。当它需要与其他同事通信时,它不是直接调用对方,而是通过自己持有的中介者引用来发起通信。
 
 
1.3 UML 类图
1.4 优缺点分析
优点:
- 降低耦合度:将对象之间多对多的复杂关系,转变为多对一的简单关系(对象对中介者)。同事对象之间不再相互引用,易于独立变化和复用。
 - 集中控制交互:所有的交互逻辑都集中在中介者中。这使得修改和扩展交互行为变得非常容易,只需修改中介者即可。
 - 符合单一职责原则:同事对象只负责自己的业务逻辑,通信的职责被剥离到了中介者中。
缺点: - 中介者可能过于复杂:随着同事对象和交互逻辑的增加,中介者会变成一个“上帝类”(God Class),包含大量逻辑,难以维护。
 - 违反开闭原则:每增加一个新的同事类型,通常都需要修改中介者的代码,以处理与新同事的交互。
 
1.5 与观察者模式的区别
中介者模式和观察者模式看起来有些相似,但它们解决的问题不同:
| 特性 | 中介者模式 | 观察者模式 | 
|---|---|---|
| 目的 | 封装一组对象之间的交互协议,防止它们互相引用。 | 定义一种一对多的依赖关系,当一个对象状态改变时,所有依赖者都会收到通知。 | 
| 焦点 | 行为协调。中介者主动管理同事间的协作流程。 | 状态通知。主题(被观察者)被动地将状态变化广播出去。 | 
| 关系 | 同事通过中介者间接通信,中介者知道所有同事。 | 观察者订阅主题,主题不知道具体的观察者是谁,只知道它们实现了某个接口。 | 
| 控制流 | 中介者主导。例如:闹钟响 -> 中介者收到 -> 中介者命令咖啡机工作。 | 主题主导。例如:温度变化 -> 主题广播 -> 所有显示器自己决定如何响应。 | 
简单来说:
- 中介者像一个指挥官,它知道所有士兵(同事),并下达命令让他们协同作战。
 - 观察者像一个公告栏,任何人(主题)都可以在上面贴通知,所有订阅了公告栏的人(观察者)都会看到,但公告栏不关心他们看到后做什么。
 
二、Go语言实现中介者模式
我们通过一个智能家居的例子来演示中介者模式。假设我们有几个设备:闹钟、咖啡机、窗帘。当闹钟响起时,我们希望咖啡机开始煮咖啡,窗帘自动拉开。这些设备不应该互相直接调用,而是通过一个“智能管家”(中介者)来协调。
2.1 步骤 1: 定义中介者和同事接口
// mediator.go
package main
// Mediator 中介者接口,定义了通知方法
type Mediator interface {Notify(sender Colleague, event string)
}
// Colleague 同事接口,定义了设置中介者的方法
type Colleague interface {SetMediator(mediator Mediator)
}
 
2.2 步骤 2: 实现具体同事
每个设备都是一个具体同事,它只关心自己的行为,并把通信请求交给中介者。
// colleagues.go
package main
import "fmt"
// Alarm 闹钟 (具体同事)
type Alarm struct {mediator Mediator
}
func NewAlarm() *Alarm {return &Alarm{}
}
func (a *Alarm) SetMediator(mediator Mediator) {a.mediator = mediator
}
func (a *Alarm) Ring() {fmt.Println("⏰ 闹钟响了!新的一天开始了!")// 闹钟不直接操作其他设备,而是通知中介者a.mediator.Notify(a, "AlarmRang")
}
// CoffeeMachine 咖啡机 (具体同事)
type CoffeeMachine struct {mediator Mediator
}
func NewCoffeeMachine() *CoffeeMachine {return &CoffeeMachine{}
}
func (c *CoffeeMachine) SetMediator(mediator Mediator) {c.mediator = mediator
}
func (c *CoffeeMachine) MakeCoffee() {fmt.Println("☕ 咖啡机开始煮咖啡...")
}
// Curtains 窗帘 (具体同事)
type Curtains struct {mediator Mediator
}
func NewCurtains() *Curtains {return &Curtains{}
}
func (c *Curtains) SetMediator(mediator Mediator) {c.mediator = mediator
}
func (c *Curtains) Open() {fmt.Println("🌤️  窗帘正在缓缓拉开...")
}
 
2.3 步骤 3: 实现具体中介者
“智能管家”是我们的具体中介者,它知道所有设备,并定义了它们之间的协作逻辑。
// smart_home_mediator.go
package main
import "fmt"
// SmartHomeMediator 智能管家 (具体中介者)
type SmartHomeMediator struct {alarm         *AlarmcoffeeMachine *CoffeeMachinecurtains      *Curtains
}
func NewSmartHomeMediator() *SmartHomeMediator {return &SmartHomeMediator{}
}
// SetAlarm 设置闹钟引用
func (shm *SmartHomeMediator) SetAlarm(alarm *Alarm) {shm.alarm = alarmalarm.SetMediator(shm)
}
// SetCoffeeMachine 设置咖啡机引用
func (shm *SmartHomeMediator) SetCoffeeMachine(coffeeMachine *CoffeeMachine) {shm.coffeeMachine = coffeeMachinecoffeeMachine.SetMediator(shm)
}
// SetCurtains 设置窗帘引用
func (shm *SmartHomeMediator) SetCurtains(curtains *Curtains) {shm.curtains = curtainscurtains.SetMediator(shm)
}
// Notify 实现中介者的核心逻辑:根据事件协调同事
func (shm *SmartHomeMediator) Notify(sender Colleague, event string) {// 根据发送者和事件类型,执行不同的业务逻辑if _, ok := sender.(*Alarm); ok && event == "AlarmRang" {fmt.Println("【智能管家】收到闹钟信号,开始执行晨间流程...")if shm.coffeeMachine != nil {shm.coffeeMachine.MakeCoffee()}if shm.curtains != nil {shm.curtains.Open()}}// 可以扩展更多逻辑,例如:// if _, ok := sender.(*TV); ok && event == "TVOn" { ... }
}
 
2.4 步骤 4: 客户端代码与演示
现在,我们把所有部分组装起来,看看中介者如何工作。
// main.go
package main
func main() {// 1. 创建中介者(智能管家)smartHome := NewSmartHomeMediator()// 2. 创建同事(各种智能设备)alarm := NewAlarm()coffeeMachine := NewCoffeeMachine()curtains := NewCurtains()// 3. 将同事注册到中介者中// 注意:这里我们通过中介者的setter来建立双向关系,并自动设置同事的中介者引用smartHome.SetAlarm(alarm)smartHome.SetCoffeeMachine(coffeeMachine)smartHome.SetCurtains(curtains)fmt.Println("--- 场景:早上7点,闹钟响起 ---")// 4. 触发一个同事的行为,观察中介者如何协调其他同事alarm.Ring()fmt.Println("\n--- 场景:如果闹钟没响,其他设备不会动 ---")// 直接调用咖啡机,它不会触发其他设备coffeeMachine.MakeCoffee()
}
 
2.5 完整代码
为了方便运行,我将所有代码都放在了一个 main.go 文件中。
// main.go
package main
import "fmt"
// ==================== 1. 定义接口 ====================
// Mediator 中介者接口,定义了通知方法
type Mediator interface {Notify(sender Colleague, event string)
}
// Colleague 同事接口,定义了设置中介者的方法
type Colleague interface {SetMediator(mediator Mediator)
}
// ==================== 2. 实现具体同事 (Colleagues) ====================
// Alarm 闹钟 (具体同事)
type Alarm struct {mediator Mediator
}
func NewAlarm() *Alarm {return &Alarm{}
}
func (a *Alarm) SetMediator(mediator Mediator) {a.mediator = mediator
}
func (a *Alarm) Ring() {fmt.Println("⏰ 闹钟响了!新的一天开始了!")// 闹钟不直接操作其他设备,而是通知中介者a.mediator.Notify(a, "AlarmRang")
}
// CoffeeMachine 咖啡机 (具体同事)
type CoffeeMachine struct {mediator Mediator
}
func NewCoffeeMachine() *CoffeeMachine {return &CoffeeMachine{}
}
func (c *CoffeeMachine) SetMediator(mediator Mediator) {c.mediator = mediator
}
func (c *CoffeeMachine) MakeCoffee() {fmt.Println("☕ 咖啡机开始煮咖啡...")
}
// Curtains 窗帘 (具体同事)
type Curtains struct {mediator Mediator
}
func NewCurtains() *Curtains {return &Curtains{}
}
func (c *Curtains) SetMediator(mediator Mediator) {c.mediator = mediator
}
func (c *Curtains) Open() {fmt.Println("🌤️  窗帘正在缓缓拉开...")
}
// ==================== 3. 实现具体中介者 (Concrete Mediator) ====================
// SmartHomeMediator 智能管家 (具体中介者)
type SmartHomeMediator struct {alarm         *AlarmcoffeeMachine *CoffeeMachinecurtains      *Curtains
}
func NewSmartHomeMediator() *SmartHomeMediator {return &SmartHomeMediator{}
}
// SetAlarm 设置闹钟引用,并建立双向关系
func (shm *SmartHomeMediator) SetAlarm(alarm *Alarm) {shm.alarm = alarmalarm.SetMediator(shm)
}
// SetCoffeeMachine 设置咖啡机引用,并建立双向关系
func (shm *SmartHomeMediator) SetCoffeeMachine(coffeeMachine *CoffeeMachine) {shm.coffeeMachine = coffeeMachinecoffeeMachine.SetMediator(shm)
}
// SetCurtains 设置窗帘引用,并建立双向关系
func (shm *SmartHomeMediator) SetCurtains(curtains *Curtains) {shm.curtains = curtainscurtains.SetMediator(shm)
}
// Notify 实现中介者的核心逻辑:根据事件协调同事
func (shm *SmartHomeMediator) Notify(sender Colleague, event string) {// 根据发送者和事件类型,执行不同的业务逻辑if _, ok := sender.(*Alarm); ok && event == "AlarmRang" {fmt.Println("【智能管家】收到闹钟信号,开始执行晨间流程...")if shm.coffeeMachine != nil {shm.coffeeMachine.MakeCoffee()}if shm.curtains != nil {shm.curtains.Open()}}// 可以扩展更多逻辑,例如:// if _, ok := sender.(*TV); ok && event == "TVOn" { ... }
}
// ==================== 4. 客户端代码与演示 ====================
func main() {// 1. 创建中介者(智能管家)smartHome := NewSmartHomeMediator()// 2. 创建同事(各种智能设备)alarm := NewAlarm()coffeeMachine := NewCoffeeMachine()curtains := NewCurtains()// 3. 将同事注册到中介者中// 注意:这里我们通过中介者的setter来建立双向关系,并自动设置同事的中介者引用smartHome.SetAlarm(alarm)smartHome.SetCoffeeMachine(coffeeMachine)smartHome.SetCurtains(curtains)fmt.Println("--- 场景:早上7点,闹钟响起 ---")// 4. 触发一个同事的行为,观察中介者如何协调其他同事alarm.Ring()fmt.Println("\n--- 场景:如果闹钟没响,其他设备不会动 ---")// 直接调用咖啡机,它不会触发其他设备coffeeMachine.MakeCoffee()
}
 
2.6 执行结果和分析
程序运行后,你将在终端看到以下输出:
--- 场景:早上7点,闹钟响起 ---
⏰ 闹钟响了!新的一天开始了!
【智能管家】收到闹钟信号,开始执行晨间流程...
☕ 咖啡机开始煮咖啡...
🌤️  窗帘正在缓缓拉开...
--- 场景:如果闹钟没响,其他设备不会动 ---
☕ 咖啡机开始煮咖啡...
 
结果分析
- 第一个场景:当 
alarm.Ring()被调用时,闹钟(同事)并不直接与咖啡机或窗帘交互。它只是通知了它的中介者smartHome。smartHome收到通知后,根据预设的逻辑(Notify方法),主动调用了coffeeMachine.MakeCoffee()和curtains.Open(),完美地协调了所有设备。这展示了中介者如何封装和控制同事间的交互。 - 第二个场景:我们绕过了中介者,直接调用了 
coffeeMachine.MakeCoffee()。可以看到,只有咖啡机自己执行了动作,窗帘没有反应。这证明了同事对象之间是解耦的,它们之间的协作必须通过中介者才能发生。 
从结果可以看出,当alarm.Ring()被调用时,它通过中介者smartHome间接触发了coffeeMachine和curtains的行为。而直接调用coffeeMachine.MakeCoffee()则不会影响其他设备。
总结:中介者模式在Go中非常适用于管理那些具有复杂网状通信关系的组件。通过引入一个中介者,你可以将混乱的“网状结构”重构为清晰的“星形结构”。
- 何时使用:当对象之间的依赖关系错综复杂,难以理解和维护时。
 - Go语言实现:利用Go的接口特性,可以非常自然地定义
Mediator和Colleague,并通过结构体组合来持有彼此的引用,实现起来非常直观。 
在构建GUI框架、消息中间件、或者复杂的业务流程引擎时,中介者模式是一个非常有价值的工具。但也要警惕其缺点,避免让中介者变得过于臃肿。
