# 工厂模式

## 工厂模式

> Factory pattern

### 知识点一：定义

定义一个工厂方法（生产者）返回一个实例对象（产品），最终让一个子类（实际生产者）去完成实例化（生产产品）。工厂模式甚至允许不使用一个构造函数，就可以提供一个通用接口来创建对象。

### 知识点二：具体例子

![image](https://3366940715-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LRk2wCgSRH-C4BHJF4E%2F-LRk4UZqXuHXIgomaEgu%2F-LRk4VPN0biOijlspRJ_%2Fjavascript-factory-method.jpg?generation=1542700011562328\&alt=media)

```javascript
//定义工厂
function Factory() {
    this.createEmployee = function (type) {
        var employee;

        if (type === "fulltime") {
            employee = new FullTime();
        } else if (type === "parttime") {
            employee = new PartTime();
        } else if (type === "temporary") {
            employee = new Temporary();
        } else if (type === "contractor") {
            employee = new Contractor();
        }

        employee.type = type;

        employee.say = function () {
            log.add(this.type + ": rate " + this.hourly + "/hour");
        }

        //生成ConcreteProduct实例
        return employee;
    }
}

//定义ConcreteProduct产品类
var FullTime = function () {
    this.hourly = "$12";
};

var PartTime = function () {
    this.hourly = "$11";
};

var Temporary = function () {
    this.hourly = "$10";
};

var Contractor = function () {
    this.hourly = "$15";
};

// log helper
var log = (function () {
    var log = "";

    return {
        add: function (msg) { log += msg + "\n"; },
        show: function () { alert(log); log = ""; }
    }
})();

function run() {
    var employees = [];
    var factory = new Factory();

    employees.push(factory.createEmployee("fulltime"));
    employees.push(factory.createEmployee("parttime"));
    employees.push(factory.createEmployee("temporary"));
    employees.push(factory.createEmployee("contractor"));

    for (var i = 0, len = employees.length; i < len; i++) {
        employees[i].say();
    }

    log.show();
}
```

#### 例子分析

_**Creator**_ 工厂，例子中的Factory 1. 此工厂实例可以生成不同种类的产品 1. 实现了一个工厂方法(factoryMethod)返回产品

_**AbstractProduct**_ 抽象产品，例子中没使用 1. 声明产品的统一接口

_**ConcreteProduct**_ 具体产品，JavaScript没有此概念 1. 被生产出来的产品 1. 所有的产品都会含有统一的接口(相同的属性或者方法)

假设有一个工厂，可以生成不同种类的工人。每一个工人有不同的时薪。createEmployee方法就是一个工厂方法。试想象一些客户端就是客户，按照不同的需要添加参数，通过调用createEmployee进行下单。工厂就会内部去实现创造一个工人实例出来。需要注意的是，由于JavaScript不支持抽象类概念，这些FullTime、PartTime等子类需要一个统一的接口（属性或者方法，例如本事例的this.hourly）。当不同种类的工人创建出来，工厂还会统一实现一些方法，例如（employee.type、employee.say）。

### 知识点三：特点

对以下使用场景特别有用&优点： 1. 当对象或组件设置比较复杂 1. 需要根据不同的环境生产不同的实例 1. 需要处理很多具有相同属性的小型对象或组件

什么情况下不应该使用&缺点： 1. 如果要实现的具体产品不具有统一的接口，使用此模式会带来很多不必要的复杂性。建议使用显式构造函数，减少不必要开销。 1. 由于对象创建的过程隐藏在接口之后抽象出来，单元测试可能会带来不便，这取决于创建过程的复杂性

## 抽象工厂（Abstract Factory）模式

### 知识点一：定义

提供一个接口（抽象工厂类）用来新建一些相似的有关联的或者互相依赖的对象（产品族），而不是通过一个明确指定的具体类

### 知识点二：具体例子

```javascript
// Types.js - Constructors used behind the scenes

// A constructor for defining new cars
class Vehicle {
  constructor(){
    this.steering = 'left'
  }
}

class Car extends Vehicle{
 constructor(options){
      // some defaults
      this.doors = options.doors || 4;
      this.state = options.state || "brand new";
      this.color = options.color || "silver";
  }
}

// A constructor for defining new trucks
class Truck extends Vehicle{
  constructor(options){
      this.state = options.state || "used";
      this.wheelSize = options.wheelSize || "large";
      this.color = options.color || "blue";
  }
}

var abstractVehicleFactory = (function () {

  // Storage for our vehicle types
  var types = {};

  return {
      getVehicle: function ( type, customizations ) {
          var _vehicle = types[type];

          return (_vehicle ? new Vehicle(customizations) : null);
      },

      registerVehicle: function ( type, _vehicle ) {
          var proto = Vehicle.prototype;

          // only register classes that fulfill the vehicle contract
          if ( proto.drive && proto.breakDown ) {
              types[type] = _vehicle;
          }

          return abstractVehicleFactory;
      }
  };
})();


// Usage:

abstractVehicleFactory.registerVehicle( "car", Car );
abstractVehicleFactory.registerVehicle( "truck", Truck );

// Instantiate a new car based on the abstract vehicle type
var car = abstractVehicleFactory.getVehicle( "car", {
            color: "lime green",
            state: "like new" } );

// Instantiate a new truck in a similar manner
var truck = abstractVehicleFactory.getVehicle( "truck", {
            wheelSize: "medium",
            color: "neon yellow" } );
```

#### 例子分析

_**AbstractFactory**_ 抽象工厂：abstractVehicleFactory 1. 声明一个接口用于生产产品

_**concreteFactory**_ 具体工厂：registerVehicle方法 1. 根据条件生产产品

_**Products**_ 产品类：Car、Truck 1. 提供要生产产品的接口给工厂

_**AbstractProduct**_ 抽象产品类：Vehicle 1. 为产品族声明接口
