FactoryMethod是一个相对比较简单的创建型模式,但是能领悟或者用对的并不多见;很多示例都没有反应出Factory Method的核心思想,只是实现了一个简化版的Abstract Factory,然后给出的解释是Factory Method模式解决“单个对象”的需求变化,Abstract Factory 模式解决“系列对象”的需求变化。
试想一下,如果把1视为N的一种特殊情况,则一个产品系列可能只包含一个对象;那么我们是不是可以认为Factory Method是一个简化版的Abstract Factory呢?实际上,Factory Method模式与Abstract Factory模式虽然同属于对象创建型模式,并且AbstractFactory类通常用Factory Method模式实现,并且效果上都可用于连接平行的类层次,但是这两个模式在思想上有着本质的区别。网上的文章抄来抄去,结果错误也被到处传。上一篇介绍了被普遍误用的Builder模式,这篇继续为Factory Method正名。
1. Factory Method (Form《设计模式》 GOF)
1.1 意图
定义一个用于创建对象的接口,让子类来决定实例化哪一个类。Factory Method使一个类的实例化延迟到其子类。
1.2 别名
虚构造器(Virtual Constructor)
1.3 实用性
在同时满足下列情况下可以使用Factory Method模式:
- 当一个类不知道他所必须创建的类的对象的时候;
- 当一个类希望由它的子类来指定他所创建的对象的时候;
- 当类将创建对象的职责委托给多个帮助子类中的某一个,并且你希望将哪一个帮助子类是代理者这一信息局部化的时候。(这个翻译很汗,英文原文是:classes delegate responsibility to one of several helper subclasses, and you want to localize the knowledge of which helper subclass is the delegate.)
1.4 结构:(直接从书中截的图)
1.5 补充说明
- 上面是GOF对FactoryMethod的阐述,字里行间,都流露出继承思想:父类(Creator)不知道他所必须创建的类的对象(Product),于是就留了个接口,委托给子类(ConcreteCreator)来实现(重写)。
- Creator类中还有个AnOperation方法,GOF还专门为这个方法做了注释,该方法是一个Template Method(模板方法),其调用FactoryMethod()。并且在相关模式中,GOF也重复提到:“工厂方法通常在Template Method中被调用”,很多介绍Factory Method的例子,都直接无视这个Method和注释,导致误用Factory Method模式。
- 这个类图中,没有画“Client”类,而Abstract Factory中,有画Client类。这里应该不是高兴的时候就画,不高兴的时候就不画,画与不画自有其中的道理。
继续沿用上一篇的思路,从几个被曲解的例子开始。
2. 两个简化版的Abstract Factory模式
2.1 Abstract Factory模式结构图
(直接从书中截的图)
2.2 当产品系列中只包含一个对象
Abstract Factory 用于创建产品系列,一个系列的产品可能包含多个(N,N>=1)个对象;但如果我们把1视为N的一种特殊情况,则一个产品系列可能只包含一个对象,则有如下结构:
1. 《大话设计模式》中Factory Method的例子:(从书中截的图)
2. <.Net设计模式(5):工厂方法模式(Factory Method)>
http://www.cnblogs.com/Terrylee/archive/2006/01/04/310716.html, 直接引用的原文中的两个图片:
由于是两个图,我也没有做处理,直接搬过来了;画上聚合线的话,与前面的例子差不多。
2.3 对这两个例子的说明
GOF在介绍Abstract Factory模式的时候,有说到“AbstractFactory类通常用Factory Method模式实现,但它们也可以用Prototype实现”。虽然上面两个例子中,只看其中的局部部分的话,用到了Factory Method模式,但结合原作者们的阐述、及扩展点,全局地看,并没有反映出Factory Method的思想。
3. Factory Method与Abstract Factory的区别
Factory Method模式与Abstract Factory模式虽然同属于对象创建型模式,并且AbstractFactory类通常用Factory Method模式实现,并且效果上都可用于连接平行的类层次(Factory Method不限于此),但是这两个模式在思想上有着本质的区别。我理解的Factory Method与Abstract Factory,有如下几点区别:
3.1 对象职责上:Abstract Factory中的Factory,只具有创建对象(一个产品系列)的唯一职责;而Factory Method中的Creator,往往具有实际的逻辑和意义
回过头来看Factory Method的结构图,可以看到Creator类中还有个AnOperation方法,GOF还专门为这个方法做了注释,该方法是一个Template Method(模板方法),其调用FactoryMethod()。并且在相关模式中,GOF也重复提到:“工厂方法通常在Template Method中被调用”。也就是说,通常是在Creator类自己的其他方法里面,调用Factory Method方法。为啥会这样子呢?
GOF用下面这个例子来引出Factory Method模式:
这个例子中,Application扮演Creator的角色,MyApplication扮演ConcreteCreator的角色。Application是一个鲜活的类,它是对具体事物(应用)的抽象,具有自己的职责,而不仅仅只是new一个Document对象;它的NewDocument方法(Template Method)调用CreateDocument,Application对象其实就是Document对象的使用者(Client);Factory Method突出的是对Procuct(本例中的Document)创建,所以在结构图中,没有出现额外的Client类,也不需要出现。
(2010-09-29补充 [特别感谢一楼的soudog] :) Factory Method的creator同时包含了不变的代码逻辑和变化部分,但里面的变化部分可以委托子类来重写(override);而abstract facotry把变化部分提取到factory类中,将不变的代码逻辑(Client)与变化部分分离得更彻底,然后通过聚合来连接Client和factory。
3.2 扩展上:Abstract Factory侧重水平扩展,而Factory Method侧重垂直扩展
“水平扩展”、“垂直扩展”的概念是我自己胡口乱说的,呵呵。可以用下面两个图来说明:
先看Abstact Factory(留意图中的红色箭头及方向):
Abstract Factory:不同的ConcreteFactory,创建不同的产品系列,Factory之间可以相互替换,从而替换产品系列。Client面向接口(AbstractFactory和AbstractProductA)编程,Factory类封装了对象系列的创建工作,具体的产品也从Client中分离开来,使得我们很容易交换产品系列。图中我们可以看到,新增加的ProductA3/B3和ConcreteFactory3,与原有ConcreteProduct和ConcreteFactory,处于平行的类层次。这就是我所谓的“水平扩展”-_-
补充:虽然可以增加其他的ConcreteFactory,譬如ConcreteFactory4,让其继承现有的ConcreteFactory,看起来ConcreteFactory4与其他的ConcreteFacotry不再属于同一个类层次,看起来不再“水平”了。但是,这里强调的是Client的视角,从Client看来,ConcreteFactory4与ConcreteFactory1~3,从意义上是等价的,即创建一个系列的产品,且他们之间可以相互替换,以实现替换产品系列。即:从逻辑意义上看,还是可以看作“水平扩展”。
再看Factory Method(留意图中的深绿色箭头及方向)::
Factory Method:子类(ConcreteCreator)重写父类(Creator)的创建产品接口(FactoryMethod())。结合Factory Method的实用性及Application/Document的例子,ConcreteCreator该创建什么类型的Product,是依赖于ConcreteCreator自身封装的逻辑来决定的(上一节介绍了,Creator/ConcreteCreator是具有现实意义的类),这里强调的是ConcreteCreator与Creator之间的继承关系,它们处于不同的类层次,这就是我所谓的“垂直扩展”。
补充:虽然可以增加ConcreteCreator3/ConcreteProduct3,如上图所示,让ConcreteCreator之间看起来不再垂直。但是,这里强调的是Creator与ConcreteCeator、以及Product与ConcreteProduct之间的关系。从Creator的Client(图中没有画出)来看,不同的ConcreteCreator之间是不能替换的。譬如Application/Document的例子,当Client操作的是一个绘图应用,则使用的必然是DrawingApplication和DrawingDocument,而不可能是TextApplication和TextDocument。虽然在“效果”一节中,GOF阐述到:Factory Method可以连接平行的类层次,并以Figure和Manipulator为例,如下图所示:
从图形结构上看,Creator(LineFigure和TextFigure等),与Product(LineManipulator和TextManipulator)之间是处于平行的类层次。但是值得注意的是:Product之间、TextManipulator之间,是不可替换的。Client面向接口(Figure和Manipulator)编程,当Client操作一条线段(Line)时,它必然要使用LineFigure和LineManipulator;而当Client操作一段文本(Text)时,也必然使用的是TextFigure和TextManipulator;ConcreteDocument依赖于Client的上下文才能确定,ConcreteFigure之间是不能相互替换的。抽象的Creator接口,不知道Client的上下文是什么,无从知晓该创建什么Product,于是就委托给子类来重写。Factory Method强调的是Procuct与ConcreteProduct、Creator与ConcreteCreator之间的继承关系,子类(ConcreteCreator)重写父类(Creator)的创建产品接口(FactoryMethod()),从逻辑意义上讲,可以看作“垂直扩展”。
而在实现一节中,GOF给出的MazeGame的例子:
图中省略了创建的产品。虽然这里可以认为EnchantedMazeGame与BomedMazeGame可以互换,但是留意MazeGame的职责(它是对具体事物[迷宫游戏]的抽象,而不仅仅是一个创建各个Product的接口),以及他的CreateMaze方法(该方法是一个模板方法)。这里反复强调的是继承父类、重写创建Product的接口。
3.3 使用上:Abstract Factory的思想是聚合(Composition),而Factory Method的思想是继承(Inheritance)
GOF在介绍完5个创建型模式后,有一个讨论小结:“用一个系统创建的那些对象的类对系统进行参数化有两种常用方式:一种是生成创建对象的类的子类,这对应于Factory Method模式”;“另一种对系统进行参数化的方法更多的依赖于对象复合:定义一个对象负责明确产品对象的类,并将它作为该系统的参数,这就是Abstract Factory、Builder和Prototype模式的关键特征。”
对于Factory Method,以Application/Document为例,Application(Creator)要使用Document(Product),把Document视为Application操作的一个“参数”,则特定的Document(ConcreteProduct)是由具体应用的Application(ConcreteCreator,Creator的子类)来创建的。
对于Abstract Factory,来看看GOF介绍的实现:
画得比较乱,凑合着看,呵呵。重点是左上方的红色注释部分,其演绎的就是Abstract Factory的Composition思想:MazeGame聚合了一个MazeFactory。
另外两个需要留意的地方:(1). 注意MazeFactory的职责:只是一组创建对象的接口;(2). MazeFactory包含了一组Factory Method。
4. 总结
Factory Method模式不是简化版的Abstract Factory;相反,Abstract Factory模式中的Factory类,可以视为一个简化版的Factory Method模式(例如本文开头的两个例子),其将Creator的职责单一化了,使之只具有创建的对象的职责。
Factory Method与Abstract Factory,在效果上都可用于连接平行的类层次(Factory Method不限于此),但是这两个模式在思想上有着本质的区别:
- 对象职责上:Abstract Factory中的Factory,只具有创建对象(一个产品系列)的唯一职责;而Factory Method中的Creator,具有实际的逻辑和意义。
- 扩展上:Abstract Factory侧重水平扩展,而Factory Method侧重垂直扩展。
- 使用上:Abstract Factory的思想是聚合,而Factory Method的思想是继承。
相关模式:Abstract Factory经常用工厂方法来实现。工厂方法通常在Template Method中被调用。