工厂模式

1. 概述

工厂模式,是将构造类的责任从类或者子类本身委托到一个新的类进行的模式;

通过将类的构建转移到一个新的类,减少了耦合度,在具体的产品需要进行修改时,只需要修改该类即可。

这个新的类被称为工厂,或者创建者(Creator)

它分为三种:

  1. 简单工厂
  2. 工厂方法
  3. 抽象工厂

2. 简单工厂

简单工厂,顾名思义,就是 简单地 将构建产品的步骤搬到另一个类中去。

需要说明的是,简单工厂在 GOF 《设计模式》中不被视为一个独立的设计模式;

但是,由于实际编程中十分常用,所以依旧有需要说明一下;

例如,一个比萨店,原来需要做比萨,烤比萨,切比萨和包装比萨:

public class PizzaStore {

    public Pizza orderPizza(String type) {
        Pizza pizza;

        // 创建比萨
        if (type.equals("cheese")) {
            pizza = new CheesePizza();
        }
        else if (type.equals("pepperoni")) {
            pizza = new PepperoniPizza();
        }

        // 其余剩下工作
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();

        return pizza;
    }
}

现在,我们使用一个简单工厂,来承接创建比萨的职能:

public class PizzaStore {
    SimpleFactory simpleFactory;

    public PizzaStore(SimpleFactor simpleFactor) {
        this.simpleFactor = simpleFactor;
    }

    public Pizza orderPizza(String type) {
        Pizza pizza;

        // 通过工厂创建比萨
        pizza = simpleFactory.createPizza(type);

        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();

        return pizza;
    }
}

public class SimpleFactory {
    public Pizza createPizza(String type) {
        Pizza pizza = null;

        if (type.equals("cheese")) {
            pizza = new CheesePizza();
        }
        else if (type.equals("pepperoni")) {
            pizza = new PepperoniPizza();
        }

        return pizza;
    }
}

3. 静态工厂

事实上,如果工厂方法无需改变;

那么我们可以将其设置为静态的:

public class Pizza{

    public static Pizza createPizza(String type) {
        Pizza pizza = null;

        if (type.equals("cheese")) {
            pizza = new CheesePizza();
        }
        else if (type.equals("pepperoni")) {
            pizza = new PepperoniPizza();
        }

        return pizza;
    }
}

实际上,静态工厂是 《Effective Java》中推荐使用的对象创建方式;

由于静态工厂拥有更为语义化的命名方式;

而且这里可以看出,静态工厂可以根据参数生成相应的子类;

而如果使用 new 关键字,则不能实例化子类。

4. 工厂方法

简单工厂虽然将产品的构建过程从客户中分离了出去;

但是,并没有解决根本问题;

比萨店还是依赖于具体的比萨类,当我们需要不同风味的比萨时,会让我们的简单工厂臃肿不堪:

public class SimpleFactory {
    public Pizza createPizza(String style, String type) {
        Pizza pizza = null;

        if (style.equals("NewYork")) {
            if (type.equals("cheese")) {
                pizza = new NYCheesePizza();
            }
            else if (type.equals("pepperoni")) {
                pizza = new NYPepperoniPizza();
            }

        }
        else if (style.equals("Chicago")) {
            if (type.equals("cheese")) {
                pizza = new ChicagoCheesePizza();
            }
            else if (type.equals("pepperoni")) {
                pizza = new ChicagoPepperoniPizza();
            }
        }
        return pizza;
    }
}

一个更好的方法是,通过派生出不同的工厂子类,让子类来实例化相应的产品:

首先,我们将 PizzaStore 设置为抽象的,并将 createPizza() 设置为抽象方法:

public abstract class PizzaStore {
    public Pizza orderPizza(String type) {
        Pizza pizza;

        pizza = createPizza(type);

        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();

        return pizza;
    }

    abstract Pizza createPizza(String type);
}

这样,PizzaStore 类就成为了 抽象的 创造者;

我们就可以通过继承 PizzaStore 来实现不同的比萨创建方式。

工厂方法模式定义了一个创建对象的接口,然后由子类来决定要实例化的类。

工厂方法让类把实例化推迟到 子类

5. 依赖倒置原则

工厂方法显著地减少了代码对于具体类的依赖;

事实上,我们应该尽量减少对具体类的依赖,由此得到新的设计原则:

要依赖抽象,不要依赖具体类

这个原则听起来很像 『针对接口编程』,不过与之不同的是,该原则更强调 抽象

也就是说,不能让高层组件(比萨店)依赖于低层组件(具体的比萨);

而是要让高层组件和低层组件 都依赖于抽象(抽象的 Pizza 类)

这里的 倒置 指的就是高层和低层组件都依赖于一个抽象类形成的依赖图倒置情况。

当我们考虑类设计时,可以先从低层组件开始设计,然后考虑抽象层,最后再考虑高层组件。

由于高层和低层组件都共同依赖于一个抽象类,所以高低层组件得到了解耦。

6. 执行倒置原则的方针

  1. 变量不可以持有具体类的引用

    使用工厂来代替持有的具体类

  2. 不要让类派生自具体类

    如果派生自具体类就会产生依赖
    请派生自一个接口或者抽象类

  3. 不要覆盖基类中已经实现的方法

    基类的方法是所有具体子类共享的

例外:对于 不可变类(immutable),可以使用 new 来进行具体类的实例化;

这是因为 不可变类 不具备改变的能力。

7. 抽象工厂

7.1 定义

抽象工厂模式提供一个 接口,用于创建相关或依赖对象的 家族,而不需要明确指明具体类。

简单的来说,抽象工厂就是 工厂的工厂

通过创造具体的工厂来创建不同的产品家族;

然后将这个产品家族提供给客户使用。

7.2 产品族

产品族指的是位于不同的产品等级结构中, 功能相关联 的产品组成的家族。

就比萨店的这里例子来说,我们具有 酱汁、面团、芝士和蛤蜊 四种不同的配料;

四种不同的配料 共同组成了配料产品族。

对于某一种配料本身,有不同的风味;
如芝加哥风味和纽约风味

7.3 使用条件

系统或者客户一次只能使用 一个 产品族;

产品之间具有良好的分级和产品族分类

7.4 重写比萨店

事实上,不同风味的比萨只是使用了不同的原料;

所以,我们只需要使用某一个地区的原料产品族就可以做出那个地区风味的比萨;

所以,我们可以使用抽象工厂重写比萨店。

首先,重写 Pizza 类,让其采用原料产品族。

public abstract class Pizza {
    String name;

    // 原料产品族
    Dough dough;
    Sauce sauce;
    Veggies veggies[];
    Cheese cheese;
    Peppperoni pepperoni;
    Clams clam;

    // 不同的比萨品种具有不同的准备方法
    abstract void prepare();
}

在具体的比萨类中,采用原料工厂。

public class CheesePizza extends Pizza {
    PizzaIngredientFactory ingredientFactory;

    @Override
    public void prepare() {
        dough = ingredientFactory.createDough();
        sauce = ingredientFactory.createSause();
        cheese = ingredientFactory.createCheese();
    }
}

在比萨店客户中,我们只需要指定某个原料工厂,即可制造出不同风味的比萨。

public class NYPizzaStore extends PizzaStore {
    protected Pizza createPizza(String item) {
        Pizza pizza = null;
        PizzaIngredientFactory ingredientFactory =
            new NYPizzaIngredientFactory();

        if (item.equals("cheese")) {
            pizza = new CheesePizza(ingredientFactory);
            pizza.setName("New York Style Cheese Pizza");
        }
        else if (item.equals("veggie")) {
            pizza = new VeggiePizza(ingredientFactory);
            pizza.setName("New York Style Veggie Pizza");
        }
    }
}

8. 总结

工厂模式是常用的对高低层平行组件进行解耦的设计模式;

通过使用 工厂抽象产品,使得客户类(高层组件)不再需要依赖于具体的产品(低层组件),而依赖于抽象产品(抽象层);

同时,低层组件通过工厂,也依赖于抽象产品,实现高低层组件的解耦。

其中,工厂方法是通过将 方法 视为工厂,将具体产品的创建职责交给子类解决;

而抽象工厂则是抽象出 工厂接口,通过不同的 具体工厂 来制作 产品族

有趣的是,工厂接口和具体工厂之间,也是使用了工厂方法模式。