bboyjing's blog

HeadFirst设计模式四【工厂模式】

本章将学习如何使用工厂模式来解放new操作符,如何从复杂的依赖关系中脱困。

场景描述

书中描述了一个订购披萨的场景,披萨有很多种,但是每一种的制作过程都一样。用户在披萨商店下单,商店根据种类来制作各种披萨。工厂模式的实现使得构造各种类型披萨变得灵活。工厂方法又细分成了三种,分别是:简单工厂、抽象工厂和工厂方法。下面就逐个来看下。

简单工厂

先学下简单工厂,看代码。

代码示例

  1. 定义披萨抽象类。为了简单,只给Pizza定义一个名称的成员变量,几个方法就是制作过程。在简化的场景下,每一种披萨的制作过是相同的,不同的是披萨的名称。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public abstract class Pizza {
    String name;
    public void prepare() {
    System.out.println("Preparing " + name);
    }
    public void bake() {
    System.out.println("Baking " + name);
    }
    public void cut() {
    System.out.println("Cutting " + name);
    }
    public void box() {
    System.out.println("Boxing " + name);
    }
    }
  2. 定义披萨实现类,下面实现两种披萨。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class CheesePizza extends Pizza {
    public CheesePizza() {
    name = "Cheese Pizza";
    }
    }
    public class VeggiePizza extends Pizza {
    public VeggiePizza() {
    name = "Veggie Pizza";
    }
    }
  3. 定义简单工厂。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class SimplePizzaFactory {
    public Pizza createPizza(String type) {
    Pizza pizza = null;
    if (type.equals("cheese")) {
    pizza = new CheesePizza();
    } else if (type.equals("veggie")) {
    pizza = new VeggiePizza();
    }
    return pizza;
    }
    }
  4. 定义一个披萨商店,用户是通过商店下单的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class PizzaStore {
    SimplePizzaFactory factory;
    public PizzaStore(SimplePizzaFactory factory) {
    this.factory = factory;
    }
    public Pizza orderPizza(String type) {
    Pizza pizza = factory.createPizza(type);
    pizza.prepare();
    pizza.bake();
    pizza.cut();
    pizza.box();
    return pizza;
    }
    }
  5. 测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class Test {
    public static void main(String[] args) {
    SimplePizzaFactory factory = new SimplePizzaFactory();
    PizzaStore store = new PizzaStore(factory);
    Pizza cheesePizza = store.orderPizza("cheese");
    System.out.println("-------------------------------");
    Pizza veggiePizza = store.orderPizza("veggie");
    }
    }

简单工厂确实挺简单的,只是把构造Pizza的代码放到一个统一的地方执行,这样的好处是只有这一个地方构造Pizza,以后实现改变时只需要修改工厂类即可。但是问题还是不支持扩展,对于新品Pizza无能为力,必须修改代码。

工厂方法

现在又有了新的需求,Pizza店要开加盟店,每家店都会有自己的风格,但是下单流程、以及制作流程要一致。所以上面只有一个工厂的简单工厂模式就不适合了,工厂方法模式把创建哪种披萨的实现直接交给了各个加盟店。下面来看代码。

代码示例

  1. 创建Pizza类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public abstract class Pizza {
    String name;
    public void prepare() {
    System.out.println("Preparing " + name);
    }
    public void bake() {
    System.out.println("Baking " + name);
    }
    public void cut() {
    System.out.println("Cutting " + name);
    }
    public void box() {
    System.out.println("Boxing " + name);
    }
    }
  2. 创建一个纽约风味的芝士披萨

    1
    2
    3
    4
    5
    public class NYStyleCheesePizza extends Pizza {
    public NYStyleCheesePizza() {
    name = "NY Style Cheese Pizza";
    }
    }
  3. 声明工厂方法,其实就是在PizzaStore变成一个抽象类,然后定一个抽象的创建Pizza的方法,所有加盟店都继承自PizzaStore,并且要实现创建Pizza的方法,这就是所谓的工厂方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public abstract class PizzaStore {
    public final Pizza orderPizza(String type) {
    Pizza pizza = creatrePizza(type);
    pizza.prepare();
    pizza.bake();
    pizza.cut();
    pizza.box();
    return pizza;
    }
    // 工厂方法
    protected abstract Pizza creatrePizza(String type);
    }
  4. 创建纽约加盟店,纽约商店里构造的是纽约风味的披萨,为了简单,这里只生产一种纽约风味的芝士披萨。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class NYPizzaStore extends PizzaStore {
    @Override
    protected Pizza createPizza(String type) {
    if (type.equals("cheese")) {
    return new NYStyleCheesePizza();
    }
    return null;
    }
    }
  5. 测试

    1
    2
    3
    4
    5
    6
    public class Test {
    public static void main(String[] args) {
    NYPizzaStore store = new NYPizzaStore();
    Pizza pizza = store.orderPizza("cheese");
    }
    }

综上,工厂方法模式定义了一个创建对象的接口,但是由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到了子类。这个模式下增加新品就不需要改代码,扩展就行了。

抽象工厂

学习抽象工厂之前,要添加一个小需求。需要在披萨里添加其他材料,比如酱料、蔬菜、芝士等。每个加盟店店口味不一样,使用的材料也不一样,要保证不能私自使用廉价材料,需要统一管理,下面看下抽象工厂如何来处理这个场景。为了演示简单,我们只在披萨中添加酱料和芝士。

代码示例

  1. 定义披萨抽象类。添加了sauce和cheese两个成员变量,分别用来保存披萨使用的是什么酱料和什么类型的那种奶酪。同时将prepare()变成抽象方法,由子类实现,在prepare时添加材料。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public abstract class Pizza {
    String name;
    String sauce;
    String cheese;
    public abstract void prepare();
    public void bake() {
    System.out.println("Baking " + name);
    }
    public void cut() {
    System.out.println("Cutting " + name);
    }
    public void box() {
    System.out.println("Boxing " + name);
    }
    }
  2. 创建原料工厂,这个就是这里要讲的抽象工厂。材料抽象工厂类定义了两个方法,分别是创建酱料和奶酪。

    1
    2
    3
    4
    public interface PizzaIngredientFactory {
    public String createSauce();
    public String createCheese();
    }
  3. 创建一个酱料工厂

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class NYPizzaIngredientFactory implements PizzaIngredientFactory {
    @Override
    public String createSauce() {
    return "NY Sauce";
    }
    @Override
    public String createCheese() {
    return "NY Cheese";
    }
    }
  4. 创建一种披萨。创建的是芝士披萨,添加了一个原料工厂成员变量,用于在prepare()中获取原材料。这样Pizza类与原料就解耦了,可以复用各种原料工厂,而不必知道原料的细节。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class CheesePizza extends Pizza {
    PizzaIngredientFactory ingredientFactory;
    public CheesePizza(PizzaIngredientFactory ingredientFactory) {
    name = "NY Style Cheese Pizza";
    this.ingredientFactory = ingredientFactory;
    }
    @Override
    public void prepare() {
    System.out.println("Preparing " + name);
    sauce = ingredientFactory.createSauce();
    cheese = ingredientFactory.createCheese();
    }
    }
  5. 创建PizzaStore工厂方法,和工厂方法模式一样

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public abstract class PizzaStore {
    public final Pizza orderPizza(String type) {
    Pizza pizza = createPizza(type);
    pizza.prepare();
    pizza.bake();
    pizza.cut();
    pizza.box();
    return pizza;
    }
    // 工厂方法
    protected abstract Pizza createPizza(String type);
    }
  6. 创建纽约披萨商店

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class NYPizzaStore extends PizzaStore {
    private PizzaIngredientFactory ingredientFactory = new NYPizzaIngredientFactory();
    @Override
    protected Pizza createPizza(String type) {
    if (type.equals("cheese")) {
    return new CheesePizza("New York Style Cheese Pizza", ingredientFactory);
    }
    return null;
    }
    }
  7. 测试

    1
    2
    3
    4
    5
    6
    public class Test {
    public static void main(String[] args) {
    PizzaStore store = new NYPizzaStore();
    store.orderPizza("cheese");
    }
    }

这里同时使用了工厂方法和抽象工厂。工厂方法用来创建PizzaStore,使用的方法是继承;抽象工厂用来创建各种原料,是通过对象组合的方式。

总结

三个工厂方法学下来可能会有点混乱,我们来理一下。第一种简单工厂最直观,就是把创建示例的代码放到一个单独的工厂类里去执行。另外两种使用场景会有点区别。工厂方法是在要实例化的父类上定一个创建对象抽象方法,所有的子类实现该方法,提供具体的实例化类的逻辑。我们把要实例化的对象分个层级的话,Pizza是一级,属于主类;而原材料是Pizza的成员变量,当做二级分类。那工厂方法处理的就是主类的实例化解耦。而抽象工厂,是需要创建产品族和想让制造的相关产品集合起来时使用的,在这个场景下就是创建一系列二级分类原材料。