Cover Image

我在上一篇关于工厂模式的文章中介绍了一种简单、直接的方式,可以用来封装创建的逻辑而不暴露过多细节。但一个问题是每当我们添加新的产品品类的时候,我们都需要修改工厂类,因此这种方式不是很灵活。另外,它要求工厂类依赖所有具体的产品类。尽管类注册技巧可以让我们在一定程度上消除这种依赖耦合,它们还是有一些明显的缺陷以至于这种使用方式没能被广泛使用。

这篇文章中,我们将探索一种更流行的模式来解决这些问题:工厂方法模式。

在看怎么解决这些问题前,我们先介绍一下工厂方法这种设计模式。

工厂方法模式

定义一个创建对象的接口,但让实现这个接口的类来决定实例化哪个类。工厂方法让类的实例化推迟到子类中进行。

——GoF:《设计模式:可复用面向对象软件的基础》

工厂方法通过在父类型中使用虚拟的“占位符”指代创建步骤来指定所有的标准的和通用的行为,然后将创建的细节代理给由客户端代码指定的子类型。换句话说,工厂方法使用抽象方法将类实例的创建逻辑封装起来,因此,总是直接创建对象的 new 操作符通常需要避免使用。

工厂方法一般来说由架构性的框架来定义,由框架的使用者来实现。

工厂方法示例图

上图中的例子显示了 CarFactory 工厂类接口,以及实现了 CarFactory 接口的三个具体的工厂实现类 BmwCarFactoryVolvoCarFactoryTeslaCarFactory,它们分别生产创建 BmwCarVolvoCarTeslaCar 这几种产品类。客户端 SimpleFactoryDemo 使用具体的工厂实现类来生产一个 Car 类型,但不需要耦合具体的轿车类型。

下面是使用示例代码:

1
2
3
public interface Car {
  void drive();
}
1
2
3
public interface CarFactory {
  Car create();
}

上面是 CarCarFactory 接口。接下来,我们来定义具体的产品类,和生产它们的具体的产品工厂。

1
2
3
4
5
6
public class BmwCar implements Car {
  @Override
  public void drive() {
    System.out.println("Calling BmwCar::drive() method.");
  }
}
1
2
3
4
5
6
public class BmwCarFactory implements CarFactory {
  @Override
  public Car create() {
    return new BmwCar();
  }
}

正如上面所看到的,具体的工厂类返回的是抽象的 Car 类型,而非具体的某一个轿车类型。

接下来,我们看客户端代码该如何使用它们。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public class FactoryMethodDemo {

  public static void main(String[] args) {

    CarFactory carFactory1 = new BmwCarFactory();
    Car car1 = carFactory1.create();
    car1.drive();
  }

}

正如上面所展示的,工厂返回抽象的 Car 类型对象后,后续就一直使用这个对象的引用。这里无需在客户端代码中耦合具体的产品类型。

为什么使用工厂方法模式?

工厂方法模式是用来解决本文开头中所提到的问题,即简单工厂模式中在有新的产品类添加到系统中来时需要修改工厂类。

什么时候我们应该使用工厂方法模式而非简单工厂模式呢?当你的代码中需要多种工厂的实现时。由于每种具体工厂类中都实现了相同的工厂接口使得生产产品的方法都返回相同的抽象类型,这可以使得客户端代码更易于替换工厂的实现。

优势和缺陷

正如上面解释的,工厂方法模式在扩展性方面拥有着很大优势,因为它在添加新类型时更为灵活。它也是软件设计原则 SOLID 中符合开闭原则一个很好的例子。

工厂方法模式也有缺点。可能你也注意到了,工厂方法模式要求有一个抽象的工厂类,并且对于每个具体产品类都要有一个具体的工厂实现类相对应。这将导致当系统中产品类型较多时类的数量会成倍增加。

现实中的例子

在 Java 中,一个描述自这篇文章的很好的例子是集合类中的 iterator() 方法。JDK 中每个集合都实现了 Iterable<E> 接口,后者定义了工厂方法 iterator() 并且返回了迭代器 Iterator<E> 这种产品类。数组 ArrayList<E> 是一种集合实现,因此它实现了 Iterable<E> 接口并且实现了它的工厂方法 iterator(),返回了 Iterator<E> 的一个子类,即一个具体的迭代器。

下面是 Java 源码中 Iterator<E> 接口的一个简化版定义:

1
2
3
4
5
public interface Iterator<E> {
    boolean hasNext();
    E next();
    void remove();
}

下面是工厂接口:

1
2
3
public interface Iterable<T> {
    Iterator<T> iterator();
}

然后是 Java 源码种数组 ArrayList 的简化版定义:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public class ArrayList<E> {
 //the iterator() returns a subtype and an "anonymous" Iterator<E>
 public Iterator<E> iterator()
   {
     return new Iterator<E>()
     {
    //implementation of the methods hasNext(), next() and remove()
     }
   }
...
}

正如上面看到的,它演示了一个具体的工厂实现类实现了工厂方法 iterator()。注意在真实的 Java 源代码中,数组 ArrayList 是继承自 AbstractList 的一个类,后者是实际上实现了工厂方法模式的类。

一个对数组 ArrayList 和它的迭代器的典型使用如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
 
public class Example {
    public static void main(String[] ars){
        // instantiation of the (concrete factory) ArrayList
        List<Integer> myArrayList = new ArrayList<>();
        
        // calling the factory method iterator() of ArrayList
        Iterator<Integer> myIterator = myArrayList.iterator();
    }
}

正如上面演示的,数组 ArrayList 返回的迭代器 Iterator 是个接口,因此对于这个迭代器的使用方式同从链表 LinkedList、哈希表 HashMap、哈希集合 HashSet 或者任何其他集合类型中获取到的迭代器都没有什么两样。这正是这种模式的强大之处:你不需要了解你正在使用哪种集合,不管是哪种都会通过它的工厂方法 iterator() 提供一个迭代器 Iterator 的实例。

结论

工厂方法模式是种很有用的设计模式,适用于当系统中存在很多具体的产品类型,且这些类型可能还在增长中时。工厂方法模式通过使用一个抽象的工厂接口和其中的工厂方法,使得所有的具体工厂实现类都生产相同的产品类型,且将产品生产逻辑细节封装在了具体的工厂实现类中。

然而,这种模式的代价是它将会带来系统中类数量的提升,因为每个具体的产品类都需要一个具体的工厂类来辅助生产。