二、工厂模式(Factory Pattern)

1.概念介绍

工厂模式的目的在于创建对象,实现下列目标:

  • 可重复执行,来创建相似对象;
  • 当编译时位置具体类型(类)时,为调用者提供一种创建对象的接口;

通过工厂方法(或类)创建的对象,都继承父对象,下面一个简单工厂方法理解:

  1. function Person(name, age, sex){
  2. let p = {}; // 或 let p = new Object(); 创建一个初始对象
  3. p.name = name;
  4. p.age = age;
  5. p.sex = sex;
  6. p.ask = function(){
  7. return 'my name is' + this.name;
  8. }
  9. return p;
  10. }
  11. let leo = new Person('leo', 18, 'boy');
  12. let pingan = new Person('pingan', 18, 'boy');
  13. console.log(leo.name, leo.age, leo.sex); // 'leo', 18, 'boy'
  14. console.log(pingan.name, pingan.age, pingan.sex); // 'pingan', 18, 'boy'

通过调用Person构造函数,我们可以像工厂那样,生产出无数个包含三个属性和一个方法的对象。
可以看出,工厂模式可以解决创建多个类似对象的问题。

2.优缺点

2.1优点

  • 一个调用者想创建一个对象,只要知道其名称就可以了。
  • 扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。
  • 屏蔽产品的具体实现,调用者只关心产品的接口。

2.2缺点

每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。

3.实现复杂工厂模式

在复杂工厂模式中,我们将其成员对象的实列化推迟到子类中,子类可以重写父类接口方法以便创建的时候指定自己的对象类型。
父类类似一个公共函数,只处理创建过程中的问题,并且这些处理将被子类继承,然后在子类实现专门功能。

比如这里我们需要实现这么一个实例:

  • 需要一个公共父函数CarMaker
  • 父函数CarMaker有个factor静态方法,用于创建car对象;
  • 定义三个静态属性,值为三个函数,用于继承父函数CarMaker

然后我们希望这么使用这个函数:

  1. let c1 = CarMaker.factory('Car1');
  2. let c2 = CarMaker.factory('Car2');
  3. let c3 = CarMaker.factory('Car3');
  4. c1.drirve(); // '我的编号是6'
  5. c2.drirve(); // '我的编号是3'
  6. c3.drirve(); // '我的编号是12'

可以看出,调用时接收以字符串形式指定类型,并返回请求类型的对象,并且这样使用是不需要用new操作符。

下面看代码实现:

  1. // 创建父构造函数
  2. function CarMaker(){};
  3. CarMaker.prototype.drive = function(){
  4. return `我的编号是${this.id}`;
  5. }
  6. // 添加静态工厂方法
  7. CarMaker.factory = function (type){
  8. let types = type, newcar;
  9. // 若构造函数不存在 则发生错误
  10. if(typeof CarMaker[types] !== 'function'){
  11. throw{ name: 'Error', message: `${types}不存在`};
  12. }
  13. // 若构造函数存在,则让原型继承父类,但仅继承一次
  14. if(CarMaker[types].prototype.drive !== 'function'){
  15. CarMaker[types].prototype = new CarMaker();
  16. }
  17. // 创建新实例,并返回
  18. newcar = new CarMaker[types]();
  19. return newcar;
  20. }
  21. // 调用
  22. CarMaker.Car1 = function(){
  23. this.id = 6;
  24. }
  25. CarMaker.Car2 = function(){
  26. this.id = 3;
  27. }
  28. CarMaker.Car3 = function(){
  29. this.id = 12;
  30. }

定义完成后,我们再执行前面的代码:

  1. let c1 = CarMaker.factory('Car1');
  2. let c2 = CarMaker.factory('Car2');
  3. let c3 = CarMaker.factory('Car3');
  4. c1.drirve(); // '我的编号是6'
  5. c2.drirve(); // '我的编号是3'
  6. c3.drirve(); // '我的编号是12'

就能正常打印结果了。

实现该工厂模式并不困难,主要是要找到能够穿件所需类型对象的构造函数。
这里使用简单的映射来创建该对象的构造函数。

4.内置对象工厂

内置的对象工厂,就像全局的Object()构造函数,也是工厂模式的行为,根据输入类型创建不同对象。
如传入一个原始数字,返回一个Number()构造函数创建一个对象,传入一个字符串或布尔值也成立。
对于传入任何其他值,包括无输入的值,都会创建一个常规的对象。

无论是否使用new操作符,都可以调用Object(),我们这么测试:

  1. let a = new Object(), b = new Object(1),
  2. c = Object('1'), d = Object(true);
  3. a.constructor === Object; // true
  4. b.constructor === Number; // true
  5. c.constructor === String; // true
  6. d.constructor === Boolean; // true

事实上,Object()用途不大,这里列出来是因为它是我们比较常见的工厂模式。

参考资料

  1. 《JavaScript Patterns》