python 设计模式之中介者模式

1. 中介者模式

中介者模式是一种行为设计模式,它解决的是对象之间互相依赖的问题,使用中介者模式,会限制对象之间的直接交互,迫使对象之间通过中介者进行合作。

举一个生活中的例子,在机场,有的飞机在停机坪上等待起飞,有的飞机在空中盘旋等待降落,这些飞机的驾驶员彼此之间不进行沟通,所有驾驶员只和塔台的调度人员沟通,由调度人员进行协调,这就是中介者模式在现实中的应用场景。

我们理解设计模式,要侧重理解它的意图,不必太纠结教条于具体实现形式,一个对象要做一件事情,需要其他对象来协调工作,中介者是这两个对象之间的沟通桥梁,避免一个对象直接去调用另一个对象。

在中介者模式中,有以下几个核心概念

  1. 组件,是包含业务逻辑的类,每个组件都维护一个指向中介者的引用,组件并不知道中介者属于哪个类,这样可以将组件与任意一个中介者建立起连接关系
  2. 中介者,是一个接口,通常只是一个通知接口
  3. 具体中介者,封装了多种组件之间的关系,通常会保存所有组件的引用。

对于一个组件,中介者对它来说是一个黑盒,当组件内有重要的事情发生时,它只能通知中介者。而中介者在收到通知后,会对通知的发送者进行甄别,确定了中介和事件后,中介者会通知相关的组件协调工作。

2. 示例代码

2.1 场景假设

在未来,智能家具普及到每一个家庭,智能家具之间可以进行协作,餐桌和餐椅都是可折叠且智能的,当我们按下餐桌的启动键,餐桌会自动展开并移动到指定位置,那么餐椅也应当如此。但我们不必亲自去启动餐椅,餐桌启动时,餐椅也会跟着着展开并移动到指定位置。

就这个场景而言,两个智能家具之间需要协作,普通的设计,餐桌对象会直接调用餐椅的work方法,而在中介者模式里,餐桌对象只会向中介者发送一个就餐的事件,由中介者通知餐椅进入工作状态。

2.2 组件

我们有两个组件需要定义,分别是智能餐桌和只能餐椅

class SmartTable():
    def __init__(self, mediator=None):
        self.mediator = mediator

    def work(self):
        print("展开餐桌并移动到餐厅指定位置")
        self.mediator.notify(self, 'work')

    def rest(self):
        print("折叠餐桌并回到指定储物位置")
        self.mediator.notify(self, 'rest')


class SmartChair():
    def __init__(self, number, mediator=None):
        self.number = number
        self.mediator = mediator

    def work(self):
        print(f"{self.number}号餐椅移动到餐厅指定位置")

    def rest(self):
        print(f"{self.number}号餐椅回到指定储物位置")

只能餐桌类里,并没有与只能餐椅相关的代码,但当餐桌工作时,我们要求餐椅要进行配合,餐桌告知中介者,我要工作了,剩下的事情,由中介者来协调

2.2 中介者接口

我们先定义一个中介者接口,用来规范具体中介者的行为

from abc import ABC

class Mediator(ABC):
    def notify(self, sender: object, event: str) -> None:
        pass
        

2.3 具体中介者

具体中介负责协调组件之间的行为

class SmartRoomMediator(Mediator):
    def __init__(self, table, chair_lst):
        self.table = table
        self.chair_lst = chair_lst
        self.table.mediator = self
        for chair in self.chair_lst:
            chair.mediator = self

    def notify(self, sender, event):
        if sender == self.table:
            if event == "work":
                for chair in self.chair_lst:
                    chair.work()
            elif event == 'rest':
                for chair in self.chair_lst:
                    chair.rest()

中介者保存了所有的组件,notify方法里第一个参数是消息的发送者,第二个参数是事件消息,餐桌可以发出work和rest两个事件。在我的这个例子中,只有餐桌可以发送事件,在更复杂的场景里,可能所有组件都能发送消息,那么在中介者的notify方法里,就需要根据sender 和 event 来决定调用哪个组件进行何种响应。

中介者封装了组件之间协作的业务逻辑,组件不知道有谁会响应自己,这一切都是由中介者决定的,这样做,正是为了减少组件之间的混乱关系,组件之间不直接调用。当协作关系发生改变时,我们可以只聚焦于中介者这一个点上,而不必理会组件会有怎样的变化。

2.4 组件之间协作

最后给出组件之间协作的示例

def client():
    table = SmartTable()
    chair_lst = []
    for i in range(4):
        chair_lst.append(SmartChair(i))

    smart_room = SmartRoomMediator(table, chair_lst)

    table.work()
    print("*"*30)
    table.rest()

if __name__ == "__main__":
    client()

程序输出结果

展开餐桌并移动到餐厅指定位置
0号餐椅移动到餐厅指定位置
1号餐椅移动到餐厅指定位置
2号餐椅移动到餐厅指定位置
3号餐椅移动到餐厅指定位置
******************************
折叠餐桌并回到指定储物位置
0号餐椅回到指定储物位置
1号餐椅回到指定储物位置
2号餐椅回到指定储物位置
3号餐椅回到指定储物位置

餐桌展开行动后,通知给中介者,中介者调用餐椅进行配合,其实这里有值得进一步讨论的话题,在中介者里直接调用餐椅的work方法和rest方法是否合理?

如果不允许中介者直接调用餐椅的work和rest方法,那么就需要为餐椅提供一个新的方法,recv ,中介者不再直接调用work和 rest方法,而是调用餐椅的recv方法,由餐椅自行决定该做什么。

按照这个思路,recv 方法里应当传入什么参数呢?显然不能有餐桌,这样就违背了中介者模式的意图,这等于是让两个组件见面了,那么就只能传event,中介者传递事件,餐椅根据事件决定调用work或者rest方法。

这样的设计,让中介者的工作更加纯粹了,但是又有点画蛇添足,根据事件决定动作的逻辑从中介者转移到了餐椅组件,如果这部分逻辑有变动,就必须修改餐椅组件。

我个人倾向于将这部分逻辑留在中介者里,组件一旦创建,我希望它是稳定的,因为它更基础,越是基础,越是希望它稳定。而中介者是我们在多个组件之上建立起来的一个管理组件之间协作关系的类,协作关系是多边的且是多变的,中介者相比于组件,更容易掌控,当组件数量固定时且内部核业务不发生变化时,我们仍然可以通过编写新的中介者来应对新的业务。

扫描关注, 与我技术互动

QQ交流群: 211426309

加入知识星球, 每天收获更多精彩内容

分享日常研究的python技术和遇到的问题及解决方案