曾经在 reddit 上看到这个 Apache 的 FactoryFactory ,觉得很好笑,只是想,大概这样的名字就是严格按照某些设计模式做出来的吧。不过笑过之后也并没有去细想。
最近自己开始用起 Java 来,因为以前学过这个语言,所以很容易就上手了,之后差不多很多东西都很自然,直到有一天我发现自己不小心做了一个叫做 FeatureCollectionFactory 的接口出来,才觉得似乎是该好好想一想了。
有一些东西,自己以前也时常听到或者看到,但是觉得太“企业级”了或者太“Java”了,并没有去关注。比如 Factory 这个东西,为什么会需要 Factory 呢?我觉得,主要就是因为 new 语句太“死板”了,比如:
Fruit fruit = new Fruit();
这里后面可以写 new Apple() 、new Pear() 或者是 new Grape() 什么的,但是无论是写任意一个,都必须要作为一个语句写死了,在编译的时候就完全确定了,但是有时候希望具体创建什么对象要等到运行时才能确定。于是就有了 Factory 。
除了这个,Factory 还可以做其他用处,比如,控制对象的创建等。然而,为何在 Ruby 或者 Python 这样的动态语言里面没有看到满天飞的 Factory 呢?原因也很简单:Factory 是为了在 Java 这样的相对静态的语言里将一些东西推迟到运行期,实现更加动态一点的特性,而这些在动态语言里是非常自然的。例如下面的 Ruby 代码:
fruit = fruit_class.new()
其中我可以在运行时将变量 fruit_class 赋值为 Apple 或者 Pear 或者(得益于 Duck Typing)其他任何合适的东西。“类”对象本身就可以当作一个 Factory 来使用了。在 Java 中虽然也有反射和 Class 对象,但是那些东西用起来实在是太笨重了,于是便有了一堆一堆的 Factory 。
再来说说最近在 Java 世界里了解到的一个叫做 Dependency Injection 的东西,很神秘的一个名字,一开始我还以为是如 InvokeDynamic 之类的东西呢,查阅了一番之后才发现其实也是一个简单的概念。
我的理解大致就是这样:原本我们写程序都希望能尽量自动化,比如,我要 new 一个 Computer 出来,Computer 会自动去查找电源,并在需要的时候 new 一个电源,电源可能又会初始化大楼的总闸、发电厂……如果有幸中间某个步骤是已经初始化好的,直接用就可以了,否则大概要追溯到宇宙大爆炸。这是一种按需取用的方式,一切都在后台自动搞定。
然而 Dependency Injection 则把这些东西反过来,比如,电源不能让 Computer 自己去查找,而是要你先把电源初始化好之后再手工传递给 Computer ,当然,在这之前你可以需要先手工打开大楼的总闸,或者之前要先建立一个发电厂……或者你得从宇宙大爆炸开始做起。
看起来好像根本就是在自找麻烦啊,对比这两种方法,我就想起了小学时解应用题的两种方法:一种是设定一个未知数,把它当作已知的东西,列出一个等式来,问题自然就解决了;另一种是直接倒推,得出一个算式就可以算出来了。后者需要很高的技巧,而且很容易算错,看起来这里的 Dependency Injection 就是在走这样的一条路,为何要把运行得好好的高度自动化的装配线拆散了手工组装呢?
问题就出在“运行得好好的”上面。很多时候程序实际上并不能那么完美地运行,一般通过事前的 Unit Testing 和事后的 Debugging 。被 Debugging 折磨了这么多年的程序员们如今都渐渐开始喜欢 Unit Testing 了,然而整个流程太自动化了,牵一发而动全身:我只是要 new 一个 Computer 而已,却有可能会触发宇宙大爆炸,而且一切都在后台进行,根本没有插手的余地,完全没有可测试性。
有了 Dependency Injection ,就变得容易多了,诸如 Computer 或者“发电厂”之类的 Java 对象是几乎完全独立的存在,需要手工把他们装配在一起。但是手工装配太痛苦了,便通过 XML 配置文件来完成(于是 Java 世界里又遍地都是 XML 和“配置文件”了),在不同的场合把不同的对象组装在一起(比如,在单元测试的时候,可以使用一个 Mocking 的发电厂)便成了一个 Java 程序。这样让我颇有些不习惯:配置文件和源代码之间的关系变得非常紧密了,因为程序的很大一部分逻辑现在跑到了配置文件里面去,仅仅靠源代码根本无法构成一个完整的程序了。
看起来 Dependency Injection 在动态语言里也是可以推行的,只是相对于成堆的 XML 来说,大概直接用脚本或者加上一些 DSL 的元素更受欢迎一些。 :)
Comments (4)
我也是从 Spring 那里了解到的,不过也许我看的文档比较旧吧,还不知道可以用 annotation 的方式。还有它说的那些什么 auto wiring 之类的,我也觉得很悬 啊,太不靠谱了…… ^_^
RoR 里面感觉很多东西都是全自动的,就是那种所谓的 Convention over Configuration ,但是单元测试之类的也可以做得很好,我觉得也许是因为 RoR 本身是一个非常特定领域的框架,它的各种行为都是预先定义好了的,所以在这个方面来说比较好做,而且易用性在这里比灵活性更重要一些了。