关于设计模式,你可能已经听说过抽象工厂(Abstract factory)和工厂方法(Factory method),在本文中我们把这两种模式统称为工厂模式。

抽象工厂模式

抽象工厂创建具有相同主题的对象。在面向对象编程中,一个工厂就是一个创建其他对象的对象。而抽象工厂把新创建对象共同的主题抽象出来。

假设我们有两个抽象工厂,它们的任务是创建页面控件,如按钮、文本框、单选按钮和列表框。一个是创建亮色控件的Light Factory,另一个是创建暗色控件的Dark Factory。两个工厂创建相同类型的控件,但是它们的颜色不同,即它们的主题不同。这是抽象工厂模式的一个实现。

你可能有个疑问,为什么不使用 new 关键字配合构造函数直接创建对象,而是要借助别的对象来创建。原因在于构造函数对于对象整体的创建过程能力有限,有时候需要交给一个具有更高视野的工厂来处理。

这些场景包括创建过程涉及到对象缓存,对象共享或复用,复杂逻辑或者应用需要维护对象和类型,以及对象需要和不同的资源和设备打交道。如果你的应用程序需要控制对象的创建过程,可以考虑用工厂模式。

打开网易新闻 查看更多图片

JavaScript不支持基于类的继承,因此图表中所示的抽象类在JavaScript示例中没有使用。抽象类和接口的作用是确保在派生类中有一致的接口。在JavaScript中,我们必须自己确保这种一致性,确保每个“具体的”对象都有与其他对象相同的接口定义(即属性和方法)。

请看下面的代码示例:

打开网易新闻 查看更多图片

在上面的例子中我们有两个具体工厂:EmployeeFactory和VendorFactory。前者创建Employee实例,后者创建Vendor实例。这两种“产品”都是人群的类型(拥有相同接口),我们可以一致对待他们。上面的数组中包含了两个员工和两个商贩。

工厂方法

它定义一个创建对象的接口,但是让子类决定实例化哪个类。工厂方法允许类将实例化推迟到子类。

工厂方法按照客户端的指示创建新对象。在JavaScript中创建对象的一种方式是用new关键字配合构造函数。然而,在有些情况下,客户端不知道或者不应该知道要实例化几个备选对象中的哪一个。工厂方法允许客户端委托对象创建,同时仍然保留对要实例化的类型的控制。

工厂方法的主要目的是可扩展性。工厂方法经常用于管理、维护或操作不同但同时具有许多共同特征(即方法和属性)的对象集合的应用程序中。一个例子是一个UI工厂会根据我们所需的类型(如按钮,输入框),返回给我们所需的具体的组件。

打开网易新闻 查看更多图片

这是一个代码示例:

打开网易新闻 查看更多图片

在这个JavaScript示例中,工厂对象创建了四种不同类型的员工。每个员工类型都有不同的小时工资。createEmployee方法是实际的工厂方法。客户端通过将类型参数传递给工厂方法来告知工厂创建什么类型的员工。

图中的AbstractProduct没有用到,因为Javascript不支持抽象类或接口。然而,我们仍然需要确保所有员工类型都有相同的接口(属性和方法)。

在这个例子中我们创建了四种不同的员工类型,它们都存储在同一个数组中。

什么时候使用工厂模式

工厂模式在应用于以下情况时尤其有用:

  • 当我们创建的对象或组件涉及到了很高的复杂度。
  • 当我们需要根据所处的环境生成不同的对象实例时。
  • 当我们处理含有相同属性的对象或组件时。
  • 当创建的对象是其他对象的实例,而且要求它们有一致的API接口时。有利于解耦。

什么时候不应该用工厂模式

如果把工厂模式应用到错误的问题类型,这种模式会给应用程序带来不必要的复杂度。除非为对象创建过程提供接口是我们正在编写的库或框架的设计目标,否则我建议总是使用构造函数来避免不必要的开销。

由于对象创建过程实际上是抽象在一个接口后面的,这也可能会带来单元测试的问题,取决于这个过程可能有多复杂。