在工厂方法模式中,介绍了一个工厂类创建一中产品,所有的工厂类都是基于接口实现的,所有的产品也是基于接口实现的。这样当增加新的产品的时候只需要实现新的工厂类和新的产品类即可,满足了开-闭原则。
但是随着产品线的复杂,很多产品并不是由一部分构成,而是由多种产品组件构成,每种产品构成一个产品族。比如,以Pizza为例,生产一个Pizza需要面饼和各种酱料(简化了,实际上需要的更多),只有面饼和酱料都准备好了才能生产一个Pizza。
这样的话,虽然我们可以继续使用工厂方法模式,为每个产品组件创建一个工厂类,但是又显得臃肿,不如将生产各个组件的工厂类合并为一个工厂类,这个总的工厂类负责生产最终的产品。以Pizza为例,可以设计一个生产Pizza的类,在这个类里面,会生产面饼和酱料。这就是抽象工厂模式(Abstract Factory Pattern)。
抽象工厂模式的简单图如下:
在上面的图中,左边表示工厂的等级结构,右边表示产品的等级结构(一共有两种产品等级结构)。
抽象工厂模式提供一个接口,使得客户端在不用指定产品的具体类型的情况下创建多个产品族中的产品对象。
通过上面的介绍,可以知道抽象工厂模式是工厂方法模式的进一步推广。下图就是抽象工厂模式的结构:
上面就是抽象工厂模式的结构图。左边是工厂的等级结构,右边是产品的等级结构。可以看出,这个抽象工厂生产的产品包括ProductA
和ProductB
,但是两个组件的种类可以不同。这样就可以相互组合。
抽象工厂模式涉及到的角色和工厂方法模式里面的角色是一样的,也是抽象工厂、具体工厂、抽象产品和具体产品,这里就不细说了。
下面是使用抽象工厂模式的代码框架:
(1)抽象工厂
public interface Factory {
//生产产品A
public ProductA createProductA();
//生产产品B
public ProductB createProductB();
}
(2)具体工厂
这里给出两个具体工厂,代表两种产品(组合了不同子产品)。
public class ConcreteFactory1 implements Factory {
public ProductA createProductA() {
return new ProductA1();
}
public ProductB createProductB() {
return new ProductB1();
}
}
public class ConcreteFactory2 implements Factory {
public ProductA createProductA() {
return new ProductA2();
}
public ProductB createProductB() {
return new ProductB2();
}
}
(3)抽象产品
有两种产品:
public interface ProductA {
}
public interface ProductB {
}
(4)具体产品
public class ProductA1 implements ProductA {
}
public class ProductA2 implements ProductA {
}
public class ProductB1 implements ProductB {
}
public class ProductB2 implements ProductB {
}
继续工厂方法模式中的Pizza店的例子。这里,Pizza店并不仅仅只是制造Pizza,也需要制造Pizza的原料。之前我们开了两家Pizza店,New York风味的和Chicago风味的。两家Pizza店都提供相同的产品族,比如芝士披萨,素食披萨,蛤蜊披萨和意式腊肠披萨等。因为两家店的风味不同,每种Pizza的原料也有所不同。
首先,重新定义一下抽象工厂(Pizza店):
public abstract class PizzaStore {
public Pizza orderPizza(String type) {
Pizza pizza;
pizza=createPizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
protected abstract Pizza createPizza(String type);
}
由于原料有很多,所以我们需要一个制造原料的工厂:
//原料工厂是一个接口,每个原料都有一个对应的方法创建该原料
public interface PizzaIngredientFactory {
public Dough createDough();
public Sauce createSauce();
public Veggies[] createVeggies();
public Pepperoni createPepperoni();
public Clams createClam();
}
现在我们要做的就是,为每一个区域(比如New York,Chicago)建造一个工厂,这个工厂需要实现PizzaIngredientFactory
接口,并实现里面创建原料的方法。
然后,我们需要提供原料类,比如ReggianoCheese
、RedPeppers
、ThickCrustDough
等。这些原料类可以在不同的区域之间共享。
创建一个New York原料工厂:
//这是New York的原料工厂,实现了PizzaIngredientFactory接口,对于每一种原料,都提供了New York风味的版本
public class NewYorkPizzaIngredientFactory implements PizzaIngredientFactory {
//这些创建原料的方法中,返回的都是抽象原料接口,new的都是具体的原料类
public Dough createDough() {
return new ThinCurstDough();
}
public Sauce createSauce() {
return new MarinaraSauce();
}
public Veggies[] createVeggies() {
Veggies[] veggies={new Garlic(),new Onion(),new Mushroom(),new RedPepper()};
return veggies;
}
public Pepperoni createPepperoni() {
return new SlicedPepperoni();
}
public Clams createClam() {
return new FreshClams();
}
}
工厂已经有了,可以开始制作Pizza了。但是和工厂方法模式里的不同,这里的Pizza需要有许多原料,适合使用抽象类,而不是接口:
//一个抽象类,表示抽象产品
public abstract class Pizza {
String name;
//这些都是Pizza的原料
Dough dough;
Sauce sauce;
Veggies[] veggies;
Cheese cheese;
Pepperoni pepperoni;
Clams clam;
//这个方法提前准备原料
abstract void prepare();
void bake() {
System.out.println("Bake for 25 minutes.");
}
void cut() {
System.out.println("Cutting the pizza into diagonal slices.");
}
void box() {
System.out.println("Place pizza in official PizzaStore box.");
}
void setName(String name) {
this.name=name;
}
String getName() {
reutrn name;
}
public String toString() {
//打印Pizza的具体信息
}
}
有了这个抽象Pizza,就可以创建具体的Pizza类了。这里先创建一个芝士披萨:
//这是一个具体类,表示芝士披萨,继承于Pizza抽象类
public class CheesePizza extends Pizza {
//每个具体的Pizza类都需要一个原料工厂,由构造参数传进来
PizzaIngredientFactory ingredientFactory;
public CheesePizza(PizzaIngredientFactory ingredientFactory) {
this.ingredientFactory=ingredientFactory;
}
//这就是prepare方法,在这个方法里,使用原料工厂创建芝士披萨所需的原料
void prepare() {
System.out.println("Preparing "+name);
dough=ingredientFactory.createDough();
sauce=ingredientFactory.createSauce();
cheese=ingredientFactory.createCheese();
}
}
从这个具体类中我们可以看到,里面使用的都是抽象的。比如原料工厂,是一个接口;使用的原料类,也都是接口。原料的具体化,是通过原料工厂的createXXX()
方法完成的。如果需要不同风味的原来,那么提供不同的原料工厂就可以了。
再来一个具体Pizza,蛤蜊披萨:
//蛤蜊披萨具体类,和芝士披萨类似,不同的是原料不一样
public class ClamPizza extends Pizza {
PizzaIngredientFactory ingredientFactory;
public ClamPizza(PizzaIngredientFactory ingredientFactory) {
this.ingredientFactory=ingredientFactory;
}
void prepare() {
System.out.println("Preparing "+name);
dough=ingredientFactory.createDough;
sauce=ingredientFactory.createSauce;
cheese=ingredientFactory.createCheese();
clam=ingredientFactory.createClam();
}
}
好了,现在原料工厂有了,具体产品类也有了,开始创建Pizza店吧:
//具体的Pizza店,首先创建一个New York的原料工厂,根据不同的类型创建不同的Pizza
public class NewYorkPizzaStore extends PizzaStore {
protected Pizza createPizza(String type) {
Pizza pizza=null;
PizzaIngredientFactory ingredientFactory=new NewYorkIngredientFactory();
if(type.equals("cheese")) {
pizza=new CheesePizza(ingredientFactory);
pizza.setName("New York Style Cheese Pizza");
} else if(type.equals("veggie")) {
pizza=new VeggiePizza(ingredientFactory);
pizza.setName("New York Style Veggie Pizza");
} else if(type.equals("clam")) {
pizza=new ClamPizza(ingredientFactory);
pizza.setName("New York Style Clam Pizza");
} else if(type.equals("pepperoni")) {
pizza=new PepperoniPizza(ingredientFactory);
pizza.setName("New York Style Pepperoni Pizza");
}
return pizza;
}
}
//这里只给出了New York的Pizza店,没有给出Chicago的具体实现,不过原理是一样的
当需要订餐时,这样就好了:
public class Client {
public static void main(String[] args) {
PizzaStore nyPizzaStore=new NewYorkPizzaStore();
Pizza nyCheesePizza=nyPizzaStore.orderPizza("cheese");
//enjoy yourself
}
}
工厂方法模式和抽象工厂模式有点相似,它们所需的角色一样,但是又有很大的不同。下面简单讨论一下:
PizzaIngredientFactory
需要实例化一样)。抽象工厂模式经常使用工厂方法模式来创建子产品(比如创建Pizza的原料)。参考资料:《Java与模式》,阎宏