为什么引入工厂模式
按照面向对象的思想,利用接口(或抽象类)实例化一群具体的相关类时,要调用new方法,在运行时根据条件决定实例化的是哪一个类,这样的话,一旦有变化或扩展,就必须重新打开这段代码进行修改(例如有新的类型要实例化),相当于将很多鸡蛋放在一个篮子里,造成维护的困难。
这是一个相似的例子(假设这些类都实现了接口Book):
Book book;
if(comic)
book = new Comic();
else if(magazine)
book = new Magazine();
else if(computer)
book = new ComputerBook();
面向对象有一个原则:“对修改关闭”,上述情况违反了这一原则,因为想用新类扩展代码的时候,需要打开它进行修改。
这种情况下,就需要引入工厂模式。工厂,顾名思义,就是用来处理对象的创建,将工厂方法封装在子类中,就可以实现解耦。下面来看一些具体的工厂方法。
简单工厂
在上面的例子中,我们可以把创建对象的部分拿出来单独放到另一个对象中,有这个对象专职创建具体对象,我们称这个新对象为“工厂”。当对象发生变化时,只需要修改这一个“工厂”就可以了。
简单工厂其实不是一个设计模式,它是一种良好的编程习惯。
工厂方法模式
正式定义
工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。
解释
在工厂方法模式中,分为创建者类和产品类两部分,在一个创建者超类中,包含一个抽象的工厂方法,让子类实现此方法去制造产品,在产品类的子类中,通过调用创建者子类的工厂方法实例化。
下面看一个制作pizza的例子:
对于pizza的制作流程,包括一个创建者(pizza店)和一个产品(pizza),假设pizza店包括纽约风味的店和芝加哥风味的店,可以在一个抽象pizza店类中定义一个抽象的工厂方法,用两个子类芝加哥和纽约风味继承它,把制作的部分放到子类实现,超类可以调用子类的工厂方法实现订购方法。如下代码所示:
package designpattern.factorypattern;
/**
* @author Zhang
* @date 2018/8/11
* @Description
*/
public abstract class PizzaStore {
protected abstract Pizza createPizza(String type);
public Pizza orderPizza(String type){
Pizza pizza; //产品对象
pizza= createPizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
具体的工厂方法在子类实现:
package designpattern.factorypattern;
/**
* @author Zhang
* @date 2018/8/11
* @Description
*/
public class NYPizzaStore extends PizzaStore {
protected Pizza createPizza(String item){
if(item.equals("cheese")){ //可以添加新的商店
return new NYStyleCheesePizza();
}
return null;
}
}
对于产品类而言,orderPizza方法返回一个pizza对象,pizza类如下所示:
package designpattern.factorypattern;
import java.util.ArrayList;
/**
* @author Zhang
* @date 2018/8/10
* @Description
*/
public class Pizza {
String name; //名称
String dough; //面团类型
String sauce; //酱料
ArrayList toppings = new ArrayList(); //调料
void prepare(){
System.out.println("Preparing " + name);
System.out.println("Tossing dough...");
System.out.println("Adding sauce...");
System.out.println("Adding toppings: ");
for(int i = 0; i < toppings.size(); i++){
System.out.println(" "+toppings.get(i));
}
}
void bake(){
System.out.println("Bake for 25 minutes at 350");
}
void cut(){
System.out.println("Cutting the pizza into diagonal slices");
}
void box(){
System.out.println("Place pizza in official PizzaStore box");
}
public String getName() {
return name;
}
}
创建者的工厂方法决定生成产品的种类,下面是pizza的一个子类:
package designpattern.factorypattern;
/**
* @author Zhang
* @date 2018/8/10
* @Description
*/
public class NYStyleCheesePizza extends Pizza {
public NYStyleCheesePizza(){
name = "NY style sauce and Cheese Pizza";
dough = "Thin Crust Dough";
sauce = "Marinara Sauce";
toppings.add("Grated Reggiano Cheese");
}
}
抽象工厂模式
抽象工厂模式提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。
抽象工厂允许客户使用抽象的接口来创建一组相关的产品,而不需要知道实际产出的具体产品是什么,这样一来,客户就从具体的产品中被解耦。
在上面的工厂类PizzaStore中,要实例化具体的Pizza类,它依赖于具体Pizza类,应该重写代码依赖抽象类,而不是具体类。具体的做法是把Pizza类变成一个抽象类或者接口,在运行时决定pizza的种类。
下面是改写后的pizza类:
package designpattern.factorypattern.abstractfactorypattern;
/**
* @author Zhang
* @date 2018/8/11
* @Description
*/
public abstract class Pizza {
String name;
Dough dough;
Sauce sauce;
Cheese cheese;
Clams clams;
abstract void prepare();
void bake(){
System.out.println("Bake for 25 minutes at 350");
}
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(){
return name;
}
}
这次,prepare()方法变成抽象的,在具体的子类中实现。原方法中所需要的原料定义成几个原料接口,派生出具体的原料种类,以cheese为例:
public class MozzarellaCheese implements Cheese {
public MozzarellaCheese(){
System.out.println("Add mozzarellaCheese");
}
}
创建一个原料工厂接口,接口中包含了创建所有原料的工厂方法,派生出具体的原料类,在一个原料工厂类中定义加载每种原料的方法,如下是一个纽约风味原料工厂:
package designpattern.factorypattern.abstractfactorypattern;
/**
* @author Zhang
* @date 2018/8/11
* @Description
*/
public class NYPizzaIngredientFactory implements PizzaIngredientFactory {
@Override
public Dough createDough(){
return new ThinCrustDough();
}
@Override
public Sauce createSauce() {
return new MarinaraSauce();
}
@Override
public Cheese createCheese() {
return new ReggianoCheese();
}
@Override
public Clams createClams() {
return new FreshClams();
}
}
在具体的pizza子类中调用具体的原料工厂方法:
package designpattern.factorypattern.abstractfactorypattern;
/**
* @author Zhang
* @date 2018/8/11
* @Description
*/
public class CheesePizza extends Pizza {
PizzaIngredientFactory pizzaIngredientFactory;
public CheesePizza(PizzaIngredientFactory pizzaIngredientFactory){
this.pizzaIngredientFactory = pizzaIngredientFactory;
}
void prepare() {
System.out.println("Prepare "+name);
dough = pizzaIngredientFactory.createDough();
sauce = pizzaIngredientFactory.createSauce();
cheese = pizzaIngredientFactory.createCheese();
}
}
这是PizzaStore的一个子类:
package designpattern.factorypattern.abstractfactorypattern;
/**
* @author Zhang
* @date 2018/8/12
* @Description
*/
public class NYPizzaStore extends PizzaStore {
@Override
protected Pizza createPizza(String type) {
Pizza pizza = null;
PizzaIngredientFactory ingredientFactory = new NYPizzaIngredientFactory();
if(type.equals("cheese")){
pizza = new CheesePizza(ingredientFactory);
pizza.setName("New York Style Cheese Pizza");
}
if (type.equals("clams")){
pizza = new ClamPizza(ingredientFactory);
pizza.setName("New York Style Clam Pizza");
}
return pizza;
}
}
这样的话,在调用具体PizzaStore的order方法时,会选择对应的Pizza类型和原料工厂。
抽象工厂和工厂方法的区别:
- 工厂方法提供一个抽象接口来创建一个产品,抽象工厂提供一个抽象接口来创建一个产品家族;
- 工厂方法使用继承,把对象的创建委托给子类,子类实现工厂方法来创建对象;抽象工厂使用对象组合,对象的创建被实现在工厂接口暴露出的方法中。