原文:https://addyosmani.com/resources/essentialjsdesignpatterns/book/#factorypatternjavascript

工厂函数是另一个与创建对象概念有关的的创建式模式。它与同类型的其他模式的区别在于它并明确的要求我们使用构造函数。相反,工厂可以提供一个普通的接口来创建对象,借助这个方法我们可以指定希望工厂创建对象的类型。

想象一下,我们有一个 UI 工厂,在工厂中我们被要求创建一种 UI 组件类型。我们不是直接使用 new 操作符或者通过其他创建构造器来创建组件,而是向工厂对象请求新组件。我们告知工厂我们需要的对象的类型是什么(如:Button、Panel),然后工厂来负责实例化它,并把实例返回给我们来使用。

这在对象的创建过程相对复杂的情况下很好用,例如,创建过程强依赖动态的因素或者程序的配置信息。

这个模式的示例可以在如 ExtJS 之类的 UI 库中找到,这些库中用于创建对象或者组件的方法可以进一步子类化。
下面的示例是基于我们之前用构造器模式创建 Car 的代码片段。它演示了如何使用工厂模式来实现一个车辆工厂:

  1. // Types.js - 定义了后续场景中要用的一些构造函数
  2. // 用于定义 Car 的构造函数
  3. function Car(options) {
  4. // 一些属性
  5. this.doors = options.doors || 4;
  6. this.state = options.state || 'brand new';
  7. this.color = options.color || 'silver';
  8. }
  9. // 用于定义 trucks 的构造函数
  10. function Truck(options) {
  11. this.state = options.state || 'used';
  12. this.wheelSize = options.wheelSize || 'large';
  13. this.color = options.color || 'blue';
  14. }
  15. // FactoryExample.js
  16. // 定义一个核心的汽车工厂
  17. function VehicleFactory() {}
  18. // 定义这个工厂的原型和对应的工具函数
  19. // 我们默认的车的类是 Car
  20. VehicleFactory.prototype.vehicleClass = Car;
  21. // 用于创建新车实例的工厂函数
  22. VehicleFactory.prototype.createVehicle = function(options) {
  23. switch (options.vehicleType) {
  24. case 'car':
  25. this.vehicleClass = Car;
  26. break;
  27. case 'truck':
  28. this.vehicleClass = Truck;
  29. break;
  30. // 默认的 VehicleFactory.prototype.vehicleClass 是 Car
  31. }
  32. return new this.vehicleClass(options);
  33. };
  34. // 创建一个制造 car 的工厂实例
  35. var carFactory = new VehicleFactory();
  36. var car = carFactory.createVehicle({
  37. vehicleType: 'car',
  38. color: 'yellow',
  39. doors: 6
  40. });
  41. // 测试我们的 car 是否是由 vehicleCalss/prototype Car 创建的
  42. // 输出: true
  43. console.log(car instanceof Car);
  44. // 输出: Car object of color "yellow", doors: 6 in a "brand new" state
  45. console.log(car);

方法一:通过修改 VehicleFactory 实例来使用 Truck 类

  1. var movingTruck = carFactory.createVehicle({
  2. vehicleType: 'truck',
  3. state: 'like new',
  4. color: 'red',
  5. wheelSize: 'small'
  6. });
  7. // 测试我们的 truck 是否是通过 vehicleClass/prototype Truck 创建的
  8. // 输出:true
  9. console.log(movingTruck instanceof Truck);
  10. // 输出:Truck object of color "red", a "like new" state and a "small" wheelSize
  11. console.log(movingTruck);

方法2:实现一个 VehicleFactory 的子类来创建一个用于建造 trucks 的工厂类

  1. function TruckFactory() {}
  2. TruckFactory.prototype = new VehicleFactory();
  3. TruckFactory.prototype.vehicleClass = Truck;
  4. var truckFactory = new TruckFactory();
  5. var myBigTruck = truckFactory.createVehicle({
  6. state: 'omg..so bad.',
  7. color: 'pink',
  8. wheelSize: 'so big'
  9. });
  10. // 确认 myBigTruck 是否是通过 Truck 原型来创建的
  11. // 输出:true
  12. console.log(myBigTruck instanceof Truck);
  13. // 输出:Truck object with the color "pink", wheelSize "so big"
  14. // and state "omg. so bad"
  15. console.log(myBigTruck);

什么时候使用工厂模式

在下面这些情况下,工厂模式尤其好用:

  • 当我们的对象或者组件的建立很复杂的时候
  • 当我们需要根据当前所处的环境来轻易的生成不同的实例对象时
  • 当我们处理很多共享着相同属性的小对象或者组件时
  • 当用只需满足特定 API 规范(也就是 “鸭子类型”)的其他对象来组成目标对象时。这对去耦合很有用。

什么时候不要使用工厂函数

当应用在错误的问题类型时,这个模式会给程序带来不必要的复杂性。除非为对象提供创造接口是我们所编写的库或者框架的目的,否则我都坚持建议显示的使用构造器来避免不必要的开销。

由于创建对象的过程是完全抽象在接口背后,这就可能会引发单元测的问题,它取决于这个过程的复杂程度。

抽象工厂

了解 抽象工厂 模式也很有用,该模式旨在将一组具有共同目标的单独工厂封装起来。它将一组对象的实现细节同其通用用法分离开来。

抽象工厂是在当系统必须独立于生成对象的方式,或者它需要使用多种类型对象时使用。

一个简单且容易理解的例子就是车辆工厂,它定义了获取和注册车辆的类型的方式。抽象工厂函数可以被命名为 abstractVehicleFactory。抽象工厂函数支持定义像 “car” 或者 “truck” 这样的车辆类型,具体的工厂只要实现满足车辆规范的类即可(如 Vehicle.prototype.driveVehicle.prototype.breakDown )。

  1. var abstractVehicleFactory = (function() {
  2. // 储存我们的车辆 类型
  3. var types = {};
  4. return {
  5. getVehicle: function(type, customizations) {
  6. var Vehicle = types[type];
  7. return Vehicle ? new Vehicle(customizations) : null;
  8. },
  9. registerVehicle: function(type, Vehicle) {
  10. var proto = Vehicle.prototype;
  11. // 只注册满足车辆规范的类
  12. if (proto.drive && proto.breakDown) {
  13. types[type] = Vehicle;
  14. }
  15. return abstractVehicleFactory;
  16. }
  17. };
  18. })();
  19. // 用法:
  20. abstractVehicleFactory.registerVehicle('car', Car);
  21. abstractVehicleFactory.registerVehicle('truck', Truck);
  22. // 基于抽象的车辆类型来实例化一个新的汽车
  23. var car = abstractVehicleFactory.getVehicle('car', {
  24. color: 'lime green',
  25. state: 'like new'
  26. });
  27. // 使用相似的方式实例化一个新的卡车
  28. var truck = abstractVehicleFactory.getVehicle('truck', {
  29. wheelSize: 'medium',
  30. color: 'neon yellow'
  31. });