二、工厂模式(Factory Pattern)
1. 概念介绍
工厂模式的目的在于创建对象,实现下列目标:
-
可重复执行,来创建相似对象;
-
当编译具体类型(类)时,为调用者提供一种创建对象的接口;
通过工厂方法(或类)创建的对象,都继承父对象,下面一个简单工厂方法理解:
function Person(name, age, sex){
let p = {}; // 或 let p = new Object(); 创建一个初始对象
p.name = name;
p.age = age;
p.sex = sex;
p.ask = function(){
return 'my name is' + this.name;
}
return p;
}
let t = new Person('Tom', 28, 'boy');
let j = new Person('Juerry', 27, 'boy');
console.log(t.name, t.age, t.sex); // 'Tom', 28, 'boy'
console.log(j.name, j.age, j.sex); // 'Juerry', 27, 'boy'
通过调用 Person构造函数,我们可以像工厂那样,生产出无数个包含三个属性和一个方法的对象。
可以看出,工厂模式可以解决创建多个类似对象的问题。
2. 优缺点
2.1 优点
-
一个调用者想创建一个对象,只要知道其名称就可以了。
-
扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。
-
屏蔽产品的具体实现,调用者只关心产品的接口。
2.2 缺点
每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。
3. 实现工厂模式
在复杂工厂模式中,我们将其成员对象的实列化推迟到子类中,子类可以重写父类接口方法以便创建的时候指定自己的对象类型。
父类类似一个公共函数,只处理创建过程中的问题,并且这些处理将被子类继承,然后在子类实现专门功能。
比如这里我们需要实现这么一个实例:
-
需要一个公共父函数
Maker; -
父函数
Maker有个createFactor静态方法,用于创建Maker对象; -
定义三个静态属性,值为三个函数,用于继承父函数
Maker;
然后我们希望这么使用这个函数:
let c1 = Maker.createFactory('c1');
let c2 = Maker.createFactory('c2');
let c3 = Maker.createFactory('c3');
c1.drive(); // '我的编号是6'
c2.drive(); // '我的编号是3'
c3.drive(); // '我的编号是12'
可以看出,调用时接收以字符串形式指定类型,并返回请求类型的对象,并且这样使用是不需要用 new操作符。
下面看粗糙版的实现:
// 创建父构造函数
function Maker() { };
Maker.prototype.drive = function () {
console.log(`我的编号是${this.id}`);
return this;
}
// 添加静态工厂方法
Maker.createFactory = function (type) {
let types = type, marker;
// 若构造函数不存在 则发生错误
if (typeof Maker[types] !== 'function') {
throw { name: 'Error', message: `${types}不存在` };
}
// 若构造函数存在,则让原型继承父类,但仅继承一次
if (Maker[types].prototype.drive !== 'function') {
Maker[types].prototype = new Maker();
}
// 创建新实例,并返回
marker = new Maker[types]();
return marker;
}
// 工厂实例
Maker.c1 = function () {
this.id = 6;
}
Maker.c2 = function () {
this.id = 3;
}
Maker.c3 = function () {
this.id = 12;
}
定义完成后,我们再执行前面的代码:
let c1 = Maker.createFactory('marker 1');
let c2 = Maker.createFactory('marker 2');
let c3 = Maker.createFactory('marker 3');
c1.drive(); // '我的编号是6'
c2.drive(); // '我的编号是3'
c3.drive(); // '我的编号是12'
就能正常打印结果了。
实现该工厂模式并不困难,主要是要找到能够穿件所需类型对象的构造函数。
这里使用简单的映射来创建该对象的构造函数。
4. JS中你可能不知道的内置对象工厂
内置的对象工厂,就像全局的 Object()构造函数,也是工厂模式的行为,根据输入类型创建不同对象。
如传入一个原始数字,返回一个 Number()构造函数创建一个对象,传入一个字符串或布尔值也成立。
对于传入任何其他值,包括无输入的值,都会创建一个常规的对象。
无论是否使用 new操作符,都可以调用 Object(),我们这么测试:
let a = new Object(), b = new Object(1),
c = Object('1'), d = Object(true);
a.constructor === Object; // true
b.constructor === Number; // true
c.constructor === String; // true
d.constructor === Boolean; // true
事实上, Object()用途不大,这里列出来是因为它是我们比较常见的工厂模式。
参考资料
- 《JavaScript Patterns》
- 《JavaScript高级程序设计》
本文使用 文章同步助手 同步