读书笔记-设计模式-可复用版-Mediator 中介者模式

发表于2019-07-01
评论0 1.21w浏览

读书笔记-设计模式-可复用版-Mediator 中介者模式


 

https://en.wikipedia.org/wiki/Mediator_pattern


 

意图:


 

用一个中介对象,来封装一系列对象的交互。中介者使各对象不需要显式的相互引用。


 

中介者模式Mediator是行为型设计模式,本来按类型,但最近在使用PureMVC,会频繁的涉及到Mediator,趁热打铁。


 

将一个系统分割成许多对象(为)通常可以增加复用性,但是对象之间相互连接的激增又会降低其可复用性,大量的相互连接使得一个对象似乎不太可能在没有其它对象的支持下工作-系统表现为一个不可分割的整体。这样,你对系统所有的行为进行较大的改动都会比较困,结构混乱,难以理解,过多的相互连接,导致对象难以为被复用,如果定义分布在多个类中的行为,又会增加许多的子类。


 

最常见的案例是UI,UI上会有许多种不同类型的控件,比如Button,Label,List,Image...等等,这些组件之间的交互可能是很紧密的,一个控件的改变可能会同时影响到其它几个控件的状态,并且,即便是相同的UI,也可能会出现不同的交互逻辑,通过不断的新增参数的形式,不是一个好的设计方式。


 

我改变交互行为(行为型),不应该影响到控件本身,说得简单一些,我改变交互行为,不应该让控件也参与编译,比如我只是改变了文本字体的大小,这并不应该引起控件的编译。


 

我记得之前在做2048项目时,有个大转盘的功能,分为两种类型,一种是广告观看,一种是免费观看,转盘内的相关逻辑也会因类型有些区别,当时就耦合在一个类中开发,这样功能就会耦合在一起,我修改其中一种类型,就会影响到另一个,这是不合理的。


 

正确的做法是将大转盘的一系列交互封装在一个Mediator中,组件之间不显式的相互引用,均由Mediator负责控制和协调, 这样,根据不同的类型去使用不同的Mediator,功能得到了拆分,对其进行修改也是互不影响的。


 

通过Mediator可以减少对象之间,相互连接的数目。这个是不是很熟悉?Facade也可以减少交互的数目。也就有了Mediator vs Facade的区别,这会在后面讲到。


 

使用Mediator也方便去替换不同的交互行为。只要替换不同的Mediator即可。


 

中介者模式Mediator其它的优点和缺点:


 

优点:

1.减少子类的生成:Mediato将原本分布于多个对象的行为集中在一起,改变这些行为只需要生成不同的Mediator的子类即可,这样,Colleague类可以得到复用。(比如组件List,Button...,没有直接和他们耦合在一起,可以复用)


 

2.将各个Colleague解耦,Mediator有利于各个Colleague间的松耦合,你可以独立的改变和复用他们。


 

3.它简化了对象的协议:Mediator可以将各Colleague间的一对多的交互来代替多对多的交互。一对多的交互更加的容易理解,维护和扩展。


 

缺点:

1.将行为集中在一起可能会让Mediator变得非常得复杂,庞大,降低了维护性,也不便于理解,这种情况通常是当交互复杂到一定程度的时候,针对这种情况,可以再进行细分,可以通过拆分成多个不同的Mediator来降低复杂度,提高维护性。


 

实现:


 

书上建议 如果当各个Collegue仅与一个Mediator进行工作时,忽略抽象的Mediator,但在框架中,我们通常会使用抽象的接口,以维护多个不同的模块的Mediator,,避免面向具体的类实现。


 

当一个Colleague发生变化的时候,其必须与Mediator进行通信,一种实现的方式是通过Observer 观察者模式,注册感兴趣的事件。


 

另一个方法是在Mediator中定义一个特殊的通知接口,各Colleague在通信时直接调用该接口,可以将自身作为一个参数传递给Mediator,使其可以识别发送。


 

但这会让Mediator和Colleagues变成双向耦合,在PUREMVC中,Colleguage并不关心Mediator的存在,没有依赖关系,所以是单向耦合。


 

另外,以UI为例,对于控件的变化,需要和Mediator进行通信,通常这种控件的状态改变都会提供一个回调接口,我们通过回调接口来实现通信也是一种不错的方式,也进行了解耦,但建议还是通过事件的形式。


 

下面详细说明下:


 

关于PureMVC Framework,当数据发生改变时,我通过发送Notification来告诉注册该事件的Mediator,进行相关UI显示的更新,这里的疑问点是:


 

有的模块,比如登录,MVC的交互可能仅限于模块内,可能并不需要其它的模块去监听数据的变化,类似于这样的情况,我们在发送Notification时,要通过Facade进行全局的转发,为何不能在Mediator的内部,模块的内部进行通知呢?


 

关于Mediator和view组件进行交互时,有两种处理机制:


 

1.在PureMVC Framework是基于Observer观察者模式实现


 

2.在书中,也提供了另外一个方法是在Mediator中定义一个特殊的”通知接口“,各个

View组件在通信时,直接调用该接口。


 

之前和同事在内部讨论的时候,有质疑过这里的设计,我当初的想法是,使用Observer观察者可以更加的简单,支持多个不同模块监听(比如View组件(即数据的改变),也同时可能导致其它的模块(比如同屏显示的其它UI),产生变化,这种情况,只能使用Observer进行通知,因为他们属于不同的Mediator之间的通信。)降低实现的复杂度。


 

但同一个Mediator内的通信,PureMVC Framework为何不采用书中说的第2种方式中,也就是当时同事的想法,我个人认为,如果View组件可以调用Mediator的内部通知接口,那么Mediator和View之间,就是双向耦合,而PureMVC Framework下,View和Mediator是单向耦合的,不利于组件的复用,View根本不关心你有多少个Mediator,这样View组件更具有复用性,也方便去替换不同的Mediator.


 

但如果要去实现,可以基于抽象耦合,即Mediator有一个抽象类,在抽象类中,定义一系列事件的通用接口,我们可以使用不同的Mediator子类去改变组件之间交互的行为。因为抽象,所以也方便我们去替换不同的Mediator。


 

但在PureMVC中,Mediator是要注册在外部Facade中的,外层实现消息的内部通信,这样,Mediator内部也实现了消息推送的话,必要性不大,除非针对这种组件,你不使用Facade进行管理,只是独立的,内部进行通信,但这样会增加复杂度,也不容易去理解。


 

另一点就是移植性,如果是以书中的想法,每一个具体的UI组件都要持有一个Mediator引用的话,但你并不知道最终会使用在哪个平台上,该平台的UI组件是否可以被继承,扩展,是否以源码的形式存在,另外,就算是可以继承扩展,你还要再单独为此生成一些子类出来,

增加工作量和复杂度。


 

内部实现通信的简要代码:


 

例子代码:


 

public class Button:Widget{

 public:

  Button (DialogDirector*);

  virtual void SetText(const char* text);

 private:

  DialogDirector* _dialogDirector;

}


 

 每一个组件里都要有一个DialogDirector(Mediator)引用。


 

可以想象,这要对当前使用的UI框架进行扩展的,而这是不可预估的。


 

下面是一些相关的助于理解的流程图:


 

640?wx_fmt=jpeg&wxfrom=5&wx_lazy=1&wx_co=1


 

简要说明:

Mediator是抽象类或接口。

ConcreteMediator是Mediator的子类

Colleague同事抽象类,这里指的是我们需要交互的对象

ConcreteColleague 和 ConcreteColleague2 是Colleague子类


 

C1和C2并不直接进行交互,他们之间的行为由ConcreteMediator负责控制和协调。


 

640?wx_fmt=jpeg&wxfrom=5&wx_lazy=1&wx_co=1
640?wx_fmt=jpeg&wxfrom=5&wx_lazy=1&wx_co=1


 

相比上面的截图,这一张要更具象化一些。

DialogDirector->Abstract Mediator

FontDialogDirector 继承自DialogDirector


 

Widget->Colleague

ListBox和 EntryField则是两个具体的组件,他们之间的交互由FontDialogDirector处理,而且Widget中持有DialogDirector的引用,用于内部消息的通信。


 

但这里可以使用delegate进行替换,会更加的方便和灵活,也可以达到解耦的目的。


 

640?wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1


 


 

上面例子的时序,通过Mediator选中某一个ListBox Item,在ListBox中持有Mediator的引用,调用WidgetChanged接口,通知组件变更,在WidgetChanged内,会调用EntryField,修改文本SetText.


 

上面都是很简单的例子,实际的项目中,我们需要进行处理交互的组件可能多达几十个,全放在一个Mediator中,会让类变得庞大难以维护,所以针对这种情况,也需要进一步的进行拆分,物极必反。


 

从上面的截图可以看出,Mediator负责控制和协调多个组件之间的交互,封装了一套“行为”,而且这些对象“组件”也可以反向的调用Mediator去做其它的处理,是双向的,这也是和Facade的主要区别。


 

举个Mediator应用的例子:


 

UI的例子是最为常见的,上面是书中的例子,我们在做游戏的时候,UI里都要用到,显示与逻辑分离,复杂的UI可能有多个View组成,将他们的交互放在Mediator中处理,减少子类的生成,简化交互的协议,提高组件的复用性,更方便去替换不同的实现。


 

stackoverflow上有一篇文章,专门有讨论Mediator vs Facade的区别,里面也提到了一个不错的案例:


 

https://stackoverflow.com/questions/481984/façade-vs-mediator


 

Mediator vs Facade:

And about the definition:

Facade's Type: Structural

Mediator's Type: Behavioral

facade more concerned about the components were contained in the  unified interface,

and mediator concern how a set of objects  interact.


 

这一段已经给出了比较明确的解释,首先两者属于不同的类型

Facade是结构型的,强调对象的组织结构

Mediator是行为型的,强调的是对象之间的交互


 

Facade是为子系统提供一个一致的,简单的接口,这里简单也说明它并不包含复杂的逻辑,仅仅是子系统中已经存在的方法进行封装,而Mediator是行为型,会改变对象之间交互的行为,所以会创建新的方法(“adds functionality”)来实现不同的交互。


 

而且Facade是单向的, Client通过Facade来和子系统进行交互,但子系统不能请求Facade,这是建立一种对象的组织结构,简化多对多的交互。 


 

在GOF书中,也对此进行了说明:


 

Facade的协议是单向的,他可以向子系统类提出请求,但反之则不行,相反,Mediator提供了各Colleague对象不支持或不能支持的协作行为,而且协议是多向的。


 


 

代码的部分就不贴了,但我个人认为目前最直观易于理解的应用还是在UI界面中,各个控件之间的交互。


 

赶上周四,晚上轩哥分享OPENGLS 方面的知识,突然想到Shader其实也扮演着Mediator的角色啊,通过对顶点信息,纹理,光照等等加工处理,可以实现非常复杂炫丽的特效,Shader是很方便进行替换的,可以实现不同的“行为”。


 

               2019.6.27 1:03 猎豹移动 王欢

  • 允许他人重新传播作品,但他人重新传播时必须在所使用作品的正文开头的显著位置,注明用户的姓名、来源及其采用的知识共享协议,并与该作品在磨坊上的原发地址建立链接
  • 可对作品重新编排、修改、节选或者以作品为基础进行创作和发布
  • 可将作品进行商业性使用

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引