Nutch正式立项以后, Apache 基金会的incubator里现存Lucene相关项目还有一个: Lucene4C. 顾名思义, 这是一个完完全全用C做成的搜索引擎, 很多对用Java做SE有意见的哥们热烈盼望这个东东快点孵化出来. 不过我担心已经已经用惯Lucene Java的同志们对Lucene4C的热情纯属叶公好龙, 理由很简单, 就像Lucene4C的说明中提到的, Lucene4C和Lucene Java的兼容是"底层兼容"即算法和索引格式的兼容, Lucene4C只是为那些惯于C代码的程序员而专门设计. 换言之, 虽然我们可以用Lucene4C搜索LuceneJava的索引或反过来用, 但绝对别指望Doug Cutting们会提供一个类似Java的接口API. 即使不提编程风格的差异, 那些只会把继承后的对象扔给API的家伙们怎么能在没有类的世界中生存呢, 没有了对象封装, 没有OCP/SRP, 还要处理种种指针, 光是调用memset/strcpy就够他们抓狂了.
从top level来讲, 将OOD的作品移植到C, 不仅是个浩大的工程, 还是个没法预期结果的定时炸弹. 不过我还是很盼望看到Lucene4C的成品, 我想抓住它的代码一行一行的读. 两种截然不同的设计理念生出一对双胞胎, 我要瞧瞧它们有什么不同, 相信Apache的天才们能让我在对比中学到很多.
所以要先搞清楚OO在Lucene中如何体现.
在利用TermQuery这个最基本的Query研讨查询机制后, 细心观察就会发现, 本来满以为用来实现查询机制的那个类Searcher, 其实和检索机制几乎毫无瓜葛, 就算有, 也不过是调用各参数的方法, 把这个作为那个的参数, 再把那个传给另外的某某. 诸如此类. Searcher是个指挥家, 负责调度各功能使之协调, 用OO的话说, 就是对象间的协作. 各对象的功能被Searcher的操作过程利用了, 至于这些功能如何实现, Searcher全然不知.
OO的一大强项是业务逻辑层面的简洁表达, 清晰的语义甚至可以用优雅来形容. 这可以视为高内聚低耦合设计的副产品. Searcher.search()方法描述的逻辑是, Query把满足查询条件的文档ID结果放到Scorer中, Scorer把结果塞进Collector. 这里还有个暗示的语义: 无论输入顺序是什么Collector中的结果总是按照score排序的. 从上述语义出发, 很容易分辨出职责的内聚以及类间的耦合关系.
具体考察一下TermQuery, 才更好理解内聚和耦合度. 在TermQuery的查询中, 对文档是否符合Term条件的判断由TermQuery负责, 而评分则由TermScorer负责. 单一职责原则SRP体现得非常明显, 而正是由于SRP被满足, Query/Scorer的耦合才得以解开.
设计的过程中, 职责内聚和抽象往往是同时完成. 所以在了解了Query/Scorer的职责后, Lucene的类结构显示出另一个十分重要的特性: 这就是开头提到的, Searcher与Query的查询过程无关. Searcher所要面对的查询是无类型的, 它需要处理各种查询. 查询的共同外部特征(在这里就是Searcher能访问的那些成员)抽象成了Query接口, Searcher与Scorer的关系也是这样. 由于使用了面向对象技术中至关重要的多态性, Searcher只面对接口(依赖倒置原则DIP), 这样就把查询的业务逻辑同各查询类型的算法逻辑有效的隔离开(解耦合)了. 这是职责内聚的结果, 同时也是把"查询"概念高度抽象的结果.
于是我们看到Searcher实现了针对"查询"的业务逻辑的描述, 对任何种类的查询, 这一业务流程都适用(抽象), 而如何实现查询的算法(结果提取和评分), 则是Query/Searcher各实现类负责(内聚). 这么做的好处不言而喻——OCP, 对扩展来说, Lucene是完全开放的, 而对更改则是封闭因而是安全的.
设想现在需要查询连续出现的Term, 单个TermQuery当然不够用, 检索算法已经变了. 但我们不需要改变现存任何一个类, 只要构建新的Query/Scorer实现类, 比如PhraseQuery/Scorer. 目前Lucene 1.4.3版已经提供这样一个检索类型.
当然, Lucene是OOD方法的杰作, 不止上述几点, 在各个实现层次上都能找到类似的设计痕迹, 这为整个软件包的扩展和应用提供了强大的架构保证. 相信这也是Lucene得以流行的深层原因.