工厂(Factory)模式

为什么引入工厂模式

按照面向对象的思想,利用接口(或抽象类)实例化一群具体的相关类时,要调用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类型和原料工厂。

抽象工厂和工厂方法的区别:

  • 工厂方法提供一个抽象接口来创建一个产品,抽象工厂提供一个抽象接口来创建一个产品家族;
  • 工厂方法使用继承,把对象的创建委托给子类,子类实现工厂方法来创建对象;抽象工厂使用对象组合,对象的创建被实现在工厂接口暴露出的方法中。

练习用的代码

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页