Delphi调停者模式(mediator模式)
作者 陈省
在项目开发中,80%的界面是非常简单的界面,比如显示一个信息对话框,显示一个操作确认对话框,简单的系统配置界面等等,另外20%是比较复杂的界面,比如用来引导所有的功能的主界面,一些业务的编辑操作界面等等,虽然这些复杂界面的总数比较少,但是这些界面的工作量却往往要占到整个界面工作量的80%,下面是一个工作量比较的示意图:


假设一个简单界面上组件平均是4个的话,同时考虑比较极端的情况下,每个组件都要和其它的组件进行通讯的话,组件之间的关联数为6个双向连接。接着假设复杂界面上平均组件的数目为8个的话,则双向关联数增加到了7+6+5+4+3+2+1=28,相当于原来的关联数目的4-5倍,如果界面组件更多的话,则关联数差距更大。
此外,界面元素过多还产生另外一个糟糕的后果,由于界面拥挤,复杂界面对于操作的方便性提出了更高的要求,也就意味着同简单界面相比,更不稳定,更容易发生变化,假设复杂界面的变化频率是简单的界面2倍的话,界面上某一个组件发生了变化,复杂界面上剩下的7个组件都需要调整位置以及修改同变化的组件的通讯代码及方式,而简单只要有3个组件进行调整。变更界面工作量比较为=7*2/3约为5倍。
综合看来,复杂界面和简单界面的工作量相比为5*4=20倍,考虑到复杂界面本身就比简单界面多一倍的组件,则工作效率差了20/2=10倍。从上面分析可以知道,一般来说在设计时应该尽可能减少你的界面复杂程度,比如将8个组件界面分解为2个4组件界面来完成,工作量可以节省不止1倍。最大的好处就是可以极大减少我们的加班时间J。
但是有的时候业务本身就很复杂,同时客户一般都会希望在一个界面做尽可能多的事情,这时界面的组件数通常不可能想减少就能减少的,那么怎么样在不减少界面元素的情况下减少组件之间的相互通讯呢?
上面的通讯模式在软件开发中是非常忌讳的蜘蛛网模式,对于复杂界面的通讯,我们通常是采用调停模式来完成,就是将组件之间的通讯集中起来,由一个调停者类来维护,所有的组件只同调停者进行通讯,这样可以极大的减少通讯关联。修改后的通讯模式示意图如下:

可以看到,采用了新的通讯模式之后,所有的通讯都通过调停者对象进行中转,通讯的关联减少到了8,比原来的28减少了2倍通讯量,同时现在复杂界面进行修改时,其它组件的通讯不用变化,需要修改的只是调停者对象了,因此变更的开销也减少了很多。但是要注意的是虽然mediator模式可以减少复杂界面的通讯,但是mediator模式不一定适用于简单界面,因为对于4组件的界面来说,采用mediator通讯的数仍然为4,没有减少,同时体系中增加了一个mediator类,无形中增加了系统架构的复杂程度。
Mediator模式的示例

假设现在我们要做一个人力资源管理系统,要求提供一个管理界面可以设定员工的属性,如名称、保险金、手机费、工资等等,如上图所示意,当用户选中任意员工时,将员工信息显示在右边的编辑框中,当用户输入新的员工属性时,自动更新列表框中的对象属性。
对于这样一个案例,采用调停者模式建模后的UML图示意如下:

其中TMediator类是调停者类,edtName、edtSalary、edtPhone、edtSafe分别是对应于员工的名称、工资、电话费、保险费的编辑输入框,lstPerson对应于员工列表框。具体类的定义如下:
type
TPerson = class(TObject)
public
Name: string;
SafeFee: Double;
PhoneFee: Double;
Salary: Double;
end;
//调停者
TMediator = class(TObject)
public
procedure ControlChanged(AControl: TControl); virtual;
end;
当组件的状态发生变化时,需要调用TMediator的ControlChanged方法来通知调停者对状态变化做出正确的响应,代码如下:
procedure TFormMediator.FormCreate(Sender: TObject);
var
APerson: TPerson;
begin
//创建mediator
FMediator := TMediator.Create;
//初始化列表,添加员工
APerson := TPerson.Create;
APerson.Name := '哈巴狗';
APerson.SafeFee := 200;
APerson.PhoneFee := 500;
APerson.Salary := 5000;
lstPerson.Items.AddObject(APerson.Name, APerson);
APerson := TPerson.Create;
APerson.Name := '大尾巴兔兔';
APerson.Salary := 50000;
APerson.SafeFee:=0;
APerson.PhoneFee:=200;
lstPerson.Items.AddObject(APerson.Name, APerson);
lstPerson.ItemIndex := 0;
//调用调停者的方法通知变更
FObjectIndex := 0;
FMediator.ControlChanged(lstPerson);
end;
{ TMediator }
procedure TMediator.ControlChanged(AControl: TControl);
begin
with FormMediator do
begin
if AControl = edtName then
begin
TPerson(lstPerson.Items.Objects[FObjectIndex]).Name:=edtName.Text;
lstPerson.Items.Strings[FObjectIndex]:=edtName.Text;
end
else if AControl = edtSafe then
begin
TPerson(lstPerson.Items.Objects[FObjectIndex]).SafeFee:=StrToFloat(edtSafe.Text);
end
else if AControl = edtPhone then
begin
TPerson(lstPerson.Items.Objects[FObjectIndex]).PhoneFee:=StrToFloat(edtPhone.Text);
end
else if AControl = edtSalary then
begin
TPerson(lstPerson.Items.Objects[FObjectIndex]).Salary:=StrToFloat(edtSalary.Text);
end
else if AControl=lstPerson then
begin
with TPerson(lstPerson.Items.Objects[FObjectIndex]) do
begin
edtName.Text:=Name;
edtSafe.Text:=floattostr(SafeFee);
edtPhone.Text:=floattostr(PhoneFee);
edtSalary.Text:=floattostr(Salary);
end;
end;
end;
end;
procedure TFormMediator.lstPersonClick(Sender: TObject);
begin
//判断是否发生变更
if lstPerson.ItemIndex <> FObjectIndex then
begin
FObjectIndex := lstPerson.ItemIndex;
FMediator.ControlChanged(lstPerson);
end;
end;
procedure TFormMediator.edtNameChange(Sender: TObject);
begin
//发生变更时,更新数据
FMediator.ControlChanged(Sender as TControl);
end;
上面过程的时序图示意如下:

调停者模式的优缺点
最主要的好处就是可以使Mediator将通讯集中化,使其管理的对象之间解偶合,每个对象只和Mediator通讯,这样当一个对象改变时,只要改变Mediator就可以了,无须改动其它对象的行为。但是反过来这也是Mediator模式的缺点,那就是Mediator本身非常复杂,不容易维护及扩展。
另外要说的是,调停者模式功能同观察者模式非常像,都是将多对多的关系转化为一对多的关系,一般来说,对于复杂的通讯模式,要么采用观察者模式,要么采用调停者模式。观察者模式同调停者模式之间的区别在于,观察者模式的目的是将通讯分散化,而调停者模式是将通讯集中化。打一个不恰当的比喻,观察者模式就相当于大学老师和学生,告诉学生有那些比较好的资料和研究领域,而学生去学什么,怎么学则交给学生自己。而观察者模式相当于小学老师和学生,老师像填鸭一样往学生的脑袋里面灌输知识,恨不得自己去帮学生学习。