软件设计要害之解耦(正文)

作软件工程师,难!

在现实中,你要努力地求偶;而在程序的世界里却要千方百计地解耦。现实中的偶,成就美满之家,而程序中的耦却带来烦恼。

序言中,村长通过组建交易委员会的方式,化解了买卖双方的困境。为此,本台记者曾经采访过村长,问他为什么会采用这样的办法,村长的回答意味深长:“以郑屠为例,他是一个专业杀猪和剔肉的,让他同时再去管理各种购买信息外加送货就会让他分心,结果是两样都干不好。增加交易委员会可以从他手上接管订货和送货两个环节,使得他不必分心而是专心地干自己擅长的分内之事。这就叫single-minded”。

我发现村长真的很有智慧,因为他无意间教给了我一种化解软件模块间耦合的重要思想。我把这种思想转化成具体的方法,实施在自己的设计中,起到了奇效。请允许我啰嗦两句,介绍一下这思想的功效。

在刚刚结束的一个软件设计中,我面临了这样一个局面:

有三个存在相互关联的模块,姑且称为A、B和C。他们需要保持同步更新,即其中任何一个模块的状态变化,必须要通知另外两者,以使其跟进,保持一致。关系如下图所示:

设计优化前的模块关系

对于这样的情况,曾经无数次地面对过,因此我不假思索地采用了最容易想到的方案。在模块A的代码中,完成关键操作后,依次调用模块B和C的对应方法进行相应的同步更新。在B和C的代码中也做了类似的事情。以A为例,代码这么写:

DoMyJob()

B.Tell("地瓜熟了")

C.Tell("地瓜熟了")

我猜有80%的开发者会走这条路,我也曾经是这80%中的一员。但是,在受到村长的启发后,我感受到了烦躁和压力。比如说:

第一、ABC三个类都要相互手持对方的实例。

第二、如果需要关联的模块有增减,需要修改很多处代码,且容易改漏掉。

第三、ABC三个模块不是真正的single-minded,除了自己的业务,他们还需要知道有谁关注他的变化,而且要亲自挨个地去通知人家,多了少了、早了晚了都不可以。好烦呢。如果模块规模不是3而是5,8,甚至更多,这代码还是人写的不是?

基于村长发明的交易委员会思想,我创建了一个专门负责管理关联的模块X,你也可以把他叫做”联络委员会模块”。有了模块X以后,全局模块关系就发生了显著变化,如下图所示:

设计优化后的结构

在X加盟以后,A、B、C三者的代码都变成这样了:

X.Order(我关注"地瓜熟了"事件)

X.Order(我关注"肉馅好了"事件)

DoMyJob()

X.Tell(我这里发生了"xxx"事件)

看到了吗,区别在哪里?每个模块会在事前主动告诉X他自己关心的事件,并把自己产生的事件通知X。而X则负责记录所有人关心的事情,并在接到事件发生的通知时告知关注者模块。区别有这些:

第一、ABC都不必相互手持对方的句柄,甚至完全不必知道对方存在。

第二、A、B、C都可以集中精力做自己的分内之事,都只需要知道“自己的事情”,所有联络的事情交给X。但其实X更单纯:有订阅就记录下来,有事件就发送给订阅者。他们都是single-minded的。

第三、X之外的模块,代码书写难度、复杂度保持稳定。模块的增减都很容易做到。而X的代码一样很简单、很机械,不论有多少个模块需要服务,都是一样的写法,可以说是最稳定的一个模块。

经过这样的一改,Wooo,令人叹为观止。代码一下子清爽了不知多少倍,我也不再担忧需要关联的模块有增减的变化了。烦恼,烟消云散了。X的一个重要本质是把coding期的耦合推迟为runtime耦合了,coding期的耦合需要工程师操心,而runtime的耦合就只需要代码自己来管理了,工程师的心就没有那么累了。

最喜欢那个模块X。看到他,就想起几年前接触过的各种电话、网络交换机和路由器,我突然发现这个X其实就是个软件事件的交换机,他的存在轻易地化解了模块之间的复杂勾连关系,解除了不必要的耦合,老衲喜欢。因此,我把这种设计上的套路私下里叫做“交换机模式”。我没有通读过大作《设计模式》,不知道里边是不是有这种结构抑或是不是也这么称呼,不过这种交换机模式的确在局部的结构上是基于订阅者模式的。

在结束之前,我们来偷看一下X模块的内部结构:

作为一名软件工程师,现实中的求偶任务已经完成了,接下来,要做好程序世界中的解耦!

你可能感兴趣的:(软件设计要害之解耦(正文))