中介者模式会限制对象之间的直接交互,迫使它们通过一个中介者对象进行合作,能减少对象之间混乱无序的依赖关系。

背景

假如有一个创建和修改客户资料的对话框,它由各种控件组成,例如文本框(TextField)、复选框(Checkbox)和按钮(Button)等。

某些表单元素可能会直接进行互动。 例如

  • 选中 “我有一只狗” 复选框后可能会显示一个隐藏文本框用于输入狗狗的名字。
  • 提交按钮必须在保存数据前校验所有输入内容。

如果直接在表单元素代码中实现业务逻辑,你将很难在程序其他表单中复用这些元素类。

  • 例如,由于复选框类与狗狗的文本框相耦合,所以将无法在其他表单中使用它。

解决方案

中介者模式建议你停止组件之间的直接交流并使其相互独立。

  • 这些组件必须调用特殊的中介者对象,通过中介者对象重定向调用行为,以间接的方式进行合作。
  • 最终组件仅依赖于一个中介者类,无需与多个其他组件相耦合。

在资料编辑表单的例子中,对话框(Dialog)类本身将作为中介者。

  • 其很可能已知自己所有的子元素,因此甚至无需在该类中引入新的依赖关系。

采用这种方式,中介者模式能在单个中介者对象中封装多个对象间的复杂关系网

  • 类所拥有的依赖关系越少,就越易于修改、扩展或复用。

中介者模式

应用场景

  • 当一些对象和其他对象紧密耦合以致难以对其进行修改时,可使用中介者模式。
    • 该模式将对象间的所有关系抽取成为一个单独的类,以使对于特定组件的修改工作独立于其他组件。
  • 当组件因过于依赖其他组件而无法在不同应用中复用时,可使用中介者模式。
    • 应用中介者模式后,每个组件不再知晓其他组件的情况。
    • 尽管这些组件无法直接交流,但它们仍可通过中介者对象进行间接交流。
  • 为了能在不同情景下复用一些基本行为,导致你需要被迫创建大量组件子类时,可使用中介者模式。
    • 由于所有组件间关系都被包含在中介者中,因此无需修改组件就能方便地新建中介者类以定义新的组件合作方式。

实现步骤

  1. 找到一组当前紧密耦合,且实现其独立性后可以带来更大好处的类(例如更易于维护或更方便复用)。
  2. 声明中介者接口并描述中介者和各种组件之间所需的交流接口。
    • 在绝大多数情况下,一个接收组件通知的方法就足够了。
    • 如果你希望在不同情景下复用组件类,那么该接口将非常重要。
    • 只要组件使用通用接口与其中介者合作,就能将该组件与不同实现中的中介者进行连接。
  3. 实现具体中介者类。该类保存了其下所有组件的引用
  4. 可以更进一步,让中介者负责组件对象的创建和销毁。此后,中介者可能会与工厂或外观类似。
  5. 组件必须保存对于中介者对象的引用。
    • 该连接通常在组件的构造函数中建立,该函数会将中介者对象作为参数传递。
  6. 修改组件代码,使其可调用中介者的通知方法,而非其他组件的方法。
    • 然后将调用其他组件的代码抽取到中介者类中,并在中介者接收到该组件通知时执行这些代码。

优缺点

优点

  • 单一职责原则。可以将多个组件间的交流抽取到同一位置,使其更易于理解和维护。
  • 开闭原则。无需修改实际组件就能增加新的中介者。
  • 可以减轻应用中多个组件间的耦合情况。
  • 可以更方便地复用各个组件。

缺点

  • 一段时间后,中介者可能会演化成为上帝对象(一个上帝对象God object是一个了解过多或者负责过多的对象。 上帝对象是反面模式的一个例子)。

与其它模式的关系

  • 责任链模式命令模式中介者模式观察者模式用于处理请求发送者和接收者之间的不同连接方式:
    • 责任链按照顺序将请求动态传递给一系列的潜在接收者,直至其中一名接收者对请求进行处理。
    • 命令在发送者和请求者之间建立单向连接。
    • 中介者清除了发送者和请求者之间的直接连接,强制它们通过一个中介对象进行间接沟通。
    • 观察者允许接收者动态地订阅或取消接收请求。
  • 外观模式和中介者的职责类似: 它们都尝试在大量紧密耦合的类中组织起合作。
    • 外观为子系统中的所有对象定义了一个简单接口,但是它不提供任何新功能。子系统本身不会意识到外观的存在。子系统中的对象可以直接进行交流。
    • 中介者将系统中组件的沟通行为中心化。各组件只知道中介者对象,互相之间无法直接交流。
  • 中介者和观察者之间的区别往往很难记住。在大部分情况下,你可以使用其中一种模式。
    • 中介者的主要目标是消除一系列系统组件之间的相互依赖。这些组件将依赖于同一个中介者对象。
      • 观察者的目标是在对象之间建立动态的单向连接,使得部分对象可作为其他对象的附属发挥作用。
    • 一种流行的中介者模式实现方式依赖于观察者。
      • 中介者对象担当发布者的角色,其他组件则作为订阅者,可以订阅中介者的事件或取消订阅。
      • 当中介者以这种方式实现时,它可能看上去与观察者非常相似。
    • 你可永久性地将所有组件链接到同一个中介者对象。这种实现方式和观察者并不相同,但这仍是一种中介者模式。
    • 假设有一个程序,其所有的组件都变成了发布者,它们之间可以相互建立动态连接。这样程序中就没有中心化的中介者对象,而只有一些分布式的观察者。

示例

中介者模式的一个绝佳例子是火车站交通系统

  • 两列火车互相之间不会就站台的空闲状态进行通信。
  • stationManager车站经理可充当中介者,让平台仅可由一列入场火车使用 而将其他火车放入队列中等待。
  • 离场火车会向车站发送通知,便于队列中的下一列火车进站。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
type train interface {
	permitArrival()
	arrive()
	depart()
}


// mediator 中介者接口
type mediator interface {
canArrive(train) bool
notifyAboutDeparture()
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import "fmt"

// passengerTrain 客运火车
type passengerTrain struct {
	mediator mediator // 持有中介者的引用
}

func (g *passengerTrain) arrive() {
	if !g.mediator.canArrive(g) {
		fmt.Println("PassengerTrain: Arrival blocked, waiting")
		return
	}
	fmt.Println("PassengerTrain: Arrived")
}

func (g *passengerTrain) depart() {
	fmt.Println("PassengerTrain: Leaving")
	g.mediator.notifyAboutDeparture()
}

func (g *passengerTrain) permitArrival() {
	fmt.Println("PassengerTrain: Arrival permitted, arriving")
	g.arrive()
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import "fmt"

// freightTrain 货运火车
type freightTrain struct {
	mediator mediator // 持有中介者的应用
}

func (g *freightTrain) arrive() {
	if !g.mediator.canArrive(g) {
		fmt.Println("FreightTrain: Arrival blocked, waiting")
		return
	}
	fmt.Println("FreightTrain: Arrived")
}

func (g *freightTrain) depart() {
	fmt.Println("FreightTrain: Leaving")
	g.mediator.notifyAboutDeparture()
}

func (g *freightTrain) permitArrival() {
	fmt.Println("FreightTrain: Arrival permitted")
	g.arrive()
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
type stationManager struct {
	isPlatformFree bool
	trainQueue     []train
}

func newStationManger() *stationManager {
	return &stationManager{
		isPlatformFree: true,
	}
}

func (s *stationManager) canArrive(t train) bool {
	if s.isPlatformFree {
		s.isPlatformFree = false
		return true
	}
	s.trainQueue = append(s.trainQueue, t)
	return false
}

func (s *stationManager) notifyAboutDeparture() {
	if !s.isPlatformFree {
		s.isPlatformFree = true
	}
	if len(s.trainQueue) > 0 {
		firstTrainInQueue := s.trainQueue[0]
		s.trainQueue = s.trainQueue[1:]
		firstTrainInQueue.permitArrival()
	}
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package main

func main() {
	stationManager := newStationManger()

	passengerTrain := &passengerTrain{
		mediator: stationManager,
	}
	freightTrain := &freightTrain{
		mediator: stationManager,
	}

	passengerTrain.arrive()
	freightTrain.arrive()
	passengerTrain.depart()
}

References

guru