版本:0.1
最后修改:2009-05-15
撰写:李现民
依赖倒置原则(DIP)告诉我们应该优先依赖于抽象类,而避免依赖于具体类。特别是在一个正在进行开发的应用程序,有很多具体都是非常易变的,因此我们应该依赖于抽象接口,以使我们钟爱大多数变化的影响。
在典型的面向抽象的程序设计逻辑中,依赖于抽象往往意味着会衍生大量的抽象类及(更大量的)子类,从而构成一些树状的类族结构。一个必然会出现的问题是:抽象类族中子类对象由谁创建?
显然,这不应该交由客户代码处理(我们不是要依赖于抽象嘛:))。一个典型的基于Factory Method解决方案结构如下:
该类图展现了这样的一个应用场景:类SomeApp通过接口Shape与ShapeFactory对产品类(Shape类族)进行操作。SomeApp完全没有使用Square类或者Circle类的任何特定方法。并且由于ShapeFactory的介入,SomeApp对这两个实现类的创建过程也一无所知。我知道您的疑问:“对具体类ShpeFactoryImplementation的依赖如果处理?”。答案是:工厂类对象往往由main或者由一个隶属于main的初始化函数创建出来。
使用Factory Method是有代价的:它很复杂,为了创建一个新类,就必须要创建出4个新类,这4个类是:2个表示该新类及其工厂的接口类,2个实现这些接口的具体类。尤其是在一个正在演化的设计的初期,如果缺省使用它们,就会棉套的增加扩展设计的难度。
另一个很常见的问题是:在整个项目的生命周期中,ShapeFactory很可能会自始至终保持仅有ShpeFactoryImplementation一个子类。结果是这带来了设计上的复杂性,但却在易于扩展性上得到实际的好处。
一个折衷的解决方案是使用Simple Factory,其类图结构如下所示:
注意Simple Factory虽然为很多设计模式的书所津津乐道,但其实并不是GOF的23种设计模式之一。同时,聪明的你一定已经发现现在ShapeFactory类已经不再是一个接口了。难道我们又陷入到对具体类的依赖中去了?哦,如果是的话,至少此时我们所依赖的具体类只有ShapeFactory一个,这并不会随着抽象类Shape子类的增多而增多。
新的问题是:除非ShapeFactory负责所有Shape对象的管理与维护(这时通常应该叫ShapeMan云云。注意:Man for manager,工厂类的命名中不一定要含有Factory字样的:)),否则它极容易成为一个贫血类----仅仅含有一个CreateShape()函数的类,它披着class的皮,干着function的事。
既然如此,何必要额外创建一个ShapeFactory对象(计算机说:这会降低效率的),把CreateShape() 交给Shape类不就得了?反正是她的孩子,也算天经地义(其实,不完全这样!)。新的类图呈现为下面的样子:
来看看我们的战果:
-
Factory职能由Shape类承担。通常CreateShape()可由一个static函数实现;
-
完全消除了非必需类,这避免了创建额外新类的代价;
-
SomeApp类仅依赖一个唯一的类Shape,并且Shape是一个抽象类,这降低了客户代码与实现类之间的耦合度;
已知的代价:
-
依赖关系环。敏感的读者一定已经发现Shape与其实现类之间形成了一种环状依赖,其代价是每添加一种新的Shape实现就必须修改CreateShape()函数的实现(能够做到不更改接口)。唯一值得庆幸的是:在Factory Method与Simple Factory的方案中,我们同样无法避免这种双向修改;
-
Shape所有权问题。尽管继承是一种比关联强得多的实体关系,但在打包时Shape最好与它的客户代码SomeApp在一起。SomeApp的实现离不开Shape,但理论上Shape却是可以脱离Shape单独存在。依赖关系环的出现会强迫Shape与它的实现类打包在一起;
-
违反了依赖倒置原则(DIP),我们的抽象类开始依赖于具体类;
-
Shape不再是一个接口。如果说Factory Method与Simple Factory中的Shape还有可能是接口的话,那么在最后一方案中具体函数CreateShape()的加入则完全打破了这种可能。
如果:在一个项目演化的初期,您还不确信您需要创建一些Factory类;或者,您已经确信可以承受该方案的代价并且期望得到它所带来的好处的话,那么您不妨尝试一下。
参考文献
-
《敏捷软件开发 原则、模式与实践》 P239:FACTORY模式;P279:ABSTRACT SERVER模式;