正如同笔者在一节里介绍的,工厂模式有简单工厂模式,工厂方法模式和抽象工厂模式几种形态。简单工厂模式已经在前面作过介绍。在简单工厂模式中,一个工厂类处于对产品类实例化调用的中心位置上,它决定那一个产品类应当被实例化, 如同一个交通警察站在来往的车辆流中,决定放行那一个方向的车辆向那一个方向流动一样。
而本节要讨论的工厂方法模式是简单工厂模式的进一步抽象化和推广。它比简单工厂模式聪明的地方在于, 它不再作为一个具体的交通警察的面貌出现,而是以交通警察局的面貌出现。它把具体的车辆交通交给下面去治理。换言之,工厂方法模式里不再只由一个工厂类决定那一个产品类应当被实例化,这个决定被交给子类去作。处于工厂方法模式的中心位置上的类甚至都不去接触那一个产品类应当被实例化这种细节。这种进一步抽象化的结果,是这种新的模式可以用来处理更加复杂的情形。
为什么需要工厂方法模式
现在,让我们继续考察我们的小花果园。在一节里,我们在后花园里引进了水果类植物, 构造了简单工厂模式来处理, 使用一个FruitGardener类来负责创立水果类的实例。见下图。
图1. 简单工厂模式。FruitGardener把握所有水果类的生杀大权。
在这一节里,我们预备再次引进蔬菜类植物,比如
西红柿 (Tomato)
土豆 (Potato)
西芥兰花 (Broccoli)
蔬菜与花和水果当然有共同点,可又有不同之处。蔬菜需要喷洒(dust)杀虫剂(pesticide)除虫, 不同的蔬菜需要喷洒不同的杀虫剂,等等。怎么办呢?
那么,再借用一下简单工厂模式不就行了? 再设计一个专管蔬菜类植物的工厂类,比如
图2. 简单工厂模式。VeggieGardener把握所有蔬菜类的生杀大权
这样做一个明显的不足点就是不够一般化和抽象化。在FruitGardener和VeggieGardener类之间明显存在很多共同点, 这些共同点应当抽出来一般化和框架化。这样一来,假如后花园的主人决定再在园子里引进些树木类植物时, 我们有框架化的处理方法。本节所要引入的工厂方法模式就符合这样的要求。
简单工厂模式的回顾
有必要首先回顾一下简单工厂模式的定义,以便于比较。
图3. 简单工厂模式的类图定义
从上图可以看出,简单工厂模式涉及到以下的角色
工厂类 (Creator)
担任这个角色的是工厂方法模式的核心,是与应用程序紧密相关的,直接在应用程序调用下,创立产品实例的那个类。工厂类只有一个,而且是实的。见下面的位图
产品 (PRodUCt)
担任这个角色的类是工厂方法模式所创立的对象的父类,或它们共同拥有的接口。
实产品 (Concrete Product)
担任这个角色的类是工厂方法模式所创立的任何对象所属的类。
实产品类可以是分布在一维数轴上的分立点 1,2,3,...中的任何一个,见下面的位图
工厂方法模式的定义
我们给出工厂方法模式的类图定义如下。
图4. 工厂方法模式的类图定义
从上图可以看出,工厂方法模式涉及到以下的角色
抽象工厂接口(Creator)
担任这个角色的是工厂方法模式的核心,它是与应用程序无关的。任何在模式中创立对象的工厂类必须实现这个接口。
实工厂类 (Conrete Creator)
担任这个角色的是与应用程序紧密相关的,直接在应用程序调用下,创立产品实例的那样一些类。
实工厂类可以是分布在一维数轴上的分立点 1,2,3,...中的任何一个,见下面的位图
产品 (Product)
担任这个角色的类是工厂方法模式所创立的对象的父类,或它们共同拥有的接口。
实产品 (Concrete Product)
担任这个角色的类是工厂方法模式所创立的任何对象所属的类。
实产品类可以是分布在二维平面上的分立点 (1,1), (1,2), (2,3),...中的任何一个,见下面的位图
由实工厂1(横数轴上第一点)创立的对象来自实产品类(1,1), (1,2), (1,3),...。由实工厂2(横数轴上第二点)创立的对象来自实产品类(2,1), (2,2), (3,3),...。依此类推
工厂方法模式和简单工厂模式在定义上的不同是很明显的。工厂方法模式的核心是一个抽象工厂类,而不像简单工厂模式, 把核心放在一个实类上。工厂方法模式可以答应很多实的工厂类从抽象工厂类继续下来, 从而可以在实际上成为多个简单工厂模式的综合,从而推广了简单工厂模式。
反过来讲,简单工厂模式是由工厂方法模式退化而来。设想假如我们非常确定一个系统只需要一个实的工厂类, 那么就不妨把抽象工厂类合并到实的工厂类中去。而这样一来,我们就退化到简单工厂模式了。
与简单工厂模式中的情形一样的是,ConcreteCreator 的factory() 方法返还的数据类型是一个接口 PlantIF,而不是哪一个具体的产品类。这种设计使得工厂类创立哪一个产品类的实例细节完全封装在工厂类内部。
工厂方法模式又叫多形性工厂模式,显然是因为实工厂类都有共同的接口,或者都有共同的抽象父类。
工厂方法模式在小花果园系统中的实现
好了,现在让我们回到小花果园的系统里,看一看怎样发挥工厂方法模式的威力,解决需要接连不断向小花果园引进不同类别的植物所带来的问题。
首先,我们需要一个抽象工厂类,比如叫做 Gardener,作为两个实工厂类 FruitGardener 及 VeggieGardener 的父类。 Gardener 的 factory() 方法可以是抽象的,留给子类去实现,也可以是实的,在父类实现一部分功能,再在子类实现剩余的功能。我们选择将 factory() 做成抽象的,主要是因为我们的系统是一个示范系统,内容十分简单,没有要在父类实现的任何功能。
图5. 工厂方法模式在小花果园系统中的实现
抽象工厂类 Gardener 是工厂方法模式的核心,但是它并不把握水果类或蔬菜类的生杀大权。相反地,这项权力被交给子类,即 VeggieGardener 及 FruitGardener。
package com.javapatterns.factorymethod;
abstract public class Gardener
{
public abstract PlantIF factory(String which) throws BadFruitException;
}
代码清单1. 父类 Gardener。
package com.javapatterns.factorymethod;
public class VeggieGardener extends Gardener
{
public PlantIF factory(String which) throws BadPlantException
{
if (which.equalsIgnoreCase("tomato"))
{
return new Tomato();
}
else if (which.equalsIgnoreCase("potato"))
{
return new Potato();
}
else if (which.equalsIgnoreCase("broccoli"))
{
return new Broccoli();
}
else
{
throw new BadPlantException("Bad veggie request");
}
}
}
代码清单2. 子类 VeggieGardener。
package com.javapatterns.factorymethod;
public class FruitGardener extends Gardener
{
public PlantIF factory(String which)
{
if (which.equalsIgnoreCase("apple"))
{
return new Apple();
}
else if (which.equalsIgnoreCase("strawberry"))
{
return new Strawberry();
}
else if (which.equalsIgnoreCase("grape"))
{
return new Grape();
}
else
{
throw new BadPlantException("Bad fruit request");
}
}
}
代码清单3. 子类 FruitGardener。
package com.javapatterns.factorymethod;
public class Broccoli implements VeggieIF, PlantIF
{
public void grow()
{
log("Broccoli is growing...");
}
public void harvest()
{
log("Broccoli has been harvested.");
}
public void plant()
{
log("Broccoli has been planted.");
}
private static void log(String msg)
{
System.out.println(msg);
}
public void pesticideDust(){ }
}
代码清单4. 蔬菜类 Broccoli。其它的蔬菜类与 Broccoli 相似,因此不再赘述。
package com.javapatterns.factorymethod;
public class Apple implements FruitIF, PlantIF
{
public void grow()
{
log("Apple is growing...");
}
public void harvest()
{
log("Apple has been harvested.");
}
public void plant()
{
log("Apple has been planted.");
}