编写可维护的代码(二)
假如一个系统中有多个模块,不妨命名为Module1, Module2, Module3......, 毫无疑问这个系统的启动过程中需要初始化所有这些模块, 而退出时要销毁它们, 那应该用下面哪种方法来完成这个任务呢?
|
B. 老老实实的一个一个的来.
|
A是典型的数据驱动 + Builder模式, 它最大的优点是增加或删除一个模块只需要增加或删除一个数据项, 耦合很小, 所以看起来非常优雅.
而A的缺点有两个. 和上一篇一样, 其中之一也出在调试上: 当一个模块初始化失败后, 如果我们只看外面这些代码, 没有办法一眼得出是谁失败了, 必须得多一些操作才行.
第二点是A实现强制了模块的初始化和退出顺序, 先初始化的模块后退出貌似很合理, 但在一个大型系统中却总会出例外, 而且还可能出现Module1先初始化一半, 然后Module2初始化, 之后Module1再继续初始化等情况. 当然, 我们可以使用"把初始化顺序和退出顺序定义在两个数组中"或"把初始化划分为多个阶段"等方法处理这些问题, 但这些方法都会增加复杂性, 而且也都不能从根本上解决问题.
B实现则用简单直接的方法很好的避免了A的问题, 虽然它看起来好像很笨, 增加删除一个模块要改多个地方, 但这些改动总共也不过几行代码, 而且往往只涉及一个文件, 所以总体代价并不高.
最后, 本文的场景乍看起来非常适合使用Builder模式, 可为什么使用它的效果不好呢? 我本人对设计模式不感冒也不擅长, 所以只能试着解释一下这个问题: 其原因就是这个场景只是看起来像, 但其实并不适用Builder模式. Builder模式要求对象支持统一的接口, 也希望对象之间没什么关联, 这是我们作设计时追求的目标, 但在实现一个复杂系统时却很难完全满足这些要求, 所以硬套上去就会出问题. 而且在实现一个系统时, 各个模块还不可能完全定下来, 实现过程中的改动也会给Buidler模式带来麻烦. 按我个人的理解, Buidler模式不应被用来处理系统的主体模块, 它真正的适用场合之一是实现对插件的支持, 把所有插件定义在一个列表中, 然后逐项处理, 因为这时系统的主体功能已经完成, 所以可以为插件定义出清晰的接口, 而且就算定义的接口有一点问题, 它所影响的也只是某些插件而非主体功能了.