JS设计模式---6.工厂模式

适用场合

动态实现

与一系列实现了同一接口、可以被同等对待的类打交道时,需要使用工厂模式

节省设置开销

如果对象需要进行复杂并且彼此相关的设置,那么使用工厂模式可以减少每种对象所需要的代码量。它可以在实例化所需要的对象之前先一次性的进行设置。
如果所用的类要求加载外部库的话,工厂方法可以对这这些库进行检查并动态加载那些未找到的库

用许多小对象组成一个大对象

工厂方法可以用来创建封装了许多小对象的对象。如果你不想让某个子系统与比较大的那个对象之间形成强耦合,而是想在运行时从许多子系统中进行挑选的话,那么使用工厂方法是比较理想的选择。

简单工厂

假设你想开几个自行车商店,每个店都有几种型号的自行车出售。这可以用一个类来表示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// BicycleFactory  单体
var BicycleFactory = {
createBicycle: function (model) {
var bicycle;
switch (model) {
case 'The Speedster':
bicycle = new Speedster();
break;
case 'The Lowrider':
bicycle = new Lowrider();
break;
case 'The Comfort Cruiser':
default:
bicycle = new ComfortCruiser
break;
}
interface.ensureImplements(bicycle,Bicycle);
return bicycle
}
}
// BicycleShop class
var BicycleShop = function () {}
BicycleShop.prototype = {
// sellBicycle根据所要求的自行车型号创建一个自行车实例,各种型号的实例可以互换使用,因为它们都实现了Bicycle接口
sellBicycle:function(model){
var bicycle = BicycleFactory.createBicycle(model);
bicycle.assemble();
bicycle.wash();
return bicycle
}
}
// Bicycle接口
var Bicycle = new Interface('Bicycle',['assemble','wash','ride','repair']);
// Speedster class
var Speedster = function () {};
Speedster.prototype = {
assemble: function () {
// ...
},
wash: function () {
// ...
},
ride:function(){
// ...
},
repair:function(){
// ...
}
}

BicycleFactory 就是简单工厂的一个例子。这种模式把成员对象的创建工作转交给一个外部对象。这个外部对象可以像本例是一个简单的命名空间,也可以是一个类的实例。

工厂模式

真正的工厂模式与简单工厂模式的区别在于,它不是另外使用一个类或对象来创建自行车,而是使用一个子类。
按照正式定义,工厂是一个将其成员对象的实例化推迟到子类中进行的类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// BicycleShop class (抽象类)
var BicycleShop = function () {};
BicycleShop.prototype = {
sellBicycle:function(model){
var bicycle = this.createBicycle(model);
bicycle.assemble();
bicycle.wash();
return bicycle;
},
createBicycle:function(model){
// 抛出错误 不能在抽象类中实例化
throw new Error('Unsupported operation on an abstract class')
}
}
// 设计一个经销特定自行车生产厂家产品的子类需要扩展BicycleShop 重新定义其中的createBicycle方法 下面为两个子类 其中一个子类代表的商店从Acme公司进货 另一个从General Products 公司进货
// AcmeBicycleShop class
var AcmeBicycleShop = function () {}
extend(AcmeBicycleShop,BicycleShop); // 继承
AcmeBicycleShop.prototype.createBicycle = function (model) {
var bicycle;
switch (model) {
case 'The Speedster':
bicycle = new AmceSpeedster();
break;
case 'The Comfort Cruiser':
default:
bicycle =new AmceComfortCruiser();
break;
}
Interface.ensureImplements(bicycle,Bicycle);
return bicycle;
}
// GeneraProductsBicycleShop class
var GeneraProductsBicycleShop = function(){};
extend(GeneraProductsBicycleShop,BicycleShop); //继承
GeneraProductsBicycleShop.prototype.createBicycle = function (model) {
var bicycle;
switch (model) {
case 'The Speedster':
bicycle = new GeneraProductsSpeedster();
break;
case 'The Comfort Cruiser':
default:
bicycle =new GeneraProductsComfortCruiser();
break;
}
Interface.ensureImplements(bicycle,Bicycle);
return bicycle;
}
// 这些工厂方法生成的对象都实现了Bicycle接口,所以在其它代码眼里他们完全可以互换
// 销售工作还是一样 只是现在所开的商店可以是Amce或者General Products自行车专卖店
var alecsCruisers = new AcmeBicycleShop();
var yourNewBike = alecsCruisers.createBicycle('The Speedster')

var bobsCruisers = new GeneraProductsBicycleShop();
var yourSecondNewBike = bobsCruisers.createBicycle('The Speedster')
// 增加对其他生产厂家的支持也很简单,只要再创建一个BicycleShop的子类并重新定义其createBicycle方法即可。 一般性的代码被集中在一个位置,而个体性的代码则被封装在子类中

一个栗子 (XHR 工厂)

  • 简单工厂
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// AjaxHandler 接口
var AjaxHandler = new Interface('AjaxHandler',['request','createXhrObject']);
// SimpleHandler classs
var SimpleHandler = function () {};
SimpleHandler.prototype = {
// reuqest 负责执行发出请求和处理响应结果所需要的一系列操作
request:function(method,url,callback,postVars){
var xhr = this.createXhrObject;
xhr.onreadystatechange = function () {
if (xhr.readyState !== 4) return;
(xhr.status ===200 )?
callback.success(xhr.reponseText,xhr.responseXML):
callback.failure(xhr.status)
};
xhr.open(method,url,true);
if (method !== 'POST') postVars = null;
xhr.send(postVars);
},
// caeateXhrObject 根据当前环境的具体情况返回一个XHR对象。在首次执行时,它会一次尝试三种用于创建XHR对象的不同方法,一旦遇到一种管用的它就回返回所创建的对象并将自身改为可以用以创建的哪个对象的函数。
//这种技术叫做memoizing,它可以提高代码的效率,因为所有设置的检测代码都只会执行一次。
caeateXhrObject:function(){
var methods = [
function () {
return new XMLHttpRequest();
},
function () {
return new ActiveXObject('Msxml2.XMLHttpRequest');
},
function () {
return new ActiveXObject('Microsoft.XMLHTTP');
},
];
for (let i = 0; i < methods.length; i++) {
try{
methods[i]()
}
catch{
continue
}
this.caeateXhrObject = methods[i];
return methods[i]
}
throw new Error('SimpleHandler: Could not create an XHR objece')
}
}
// 使用
var myHandler = new SimpleHandler();
var callback = {
success:function(reponseText){
// ...
},
failure:function(statusCode){
// ...
}
};
myHandler.request('GET','script.php',callback)
  • 专用型连接对象

    这个栗子可以进一步扩展,把工厂模式用在两个地方,以便根据网络条件创建专门的请求对象。
    QueueHandler 会在发起新的请求之前先确保所有的请求都已经成功处理,而 OfflineHandler 则会在用户处于离线状态时把请求缓存起来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
// QueueHandler class 在发起新的请求之前先确保所有请求已经成功处理
var QueueHandler = function () {
this.queue = [];
this.requestInProgress = false;
this.retryDelay = 5 // 秒
}
extend(QueueHandler,SimpleHandler);
QueueHandler.prototype.request = function (method,url,callback,postVars,override) {
if (this.requestInProgress && !override) { // 如果有请求没有处理成功,把本次请求暂存
this.queue.push({
method,
url,
callback,
postVars
})
}
else{ // 如果所有请求都已成功 发送本次请求
this.requestInProgress = true;
var xhr = this.createXhrObject();
var that = this;
xhr.onreadystatechange = function () {
if (xhr.readyState !== 4) return;
if (xhr.status === 200) {
callback.success(xhr.reponseText,xhr.responseXML);
that.advanceQueue();
}
else{
callback.failure(xhr.status);
// 5s后再次发起请求
setTimeout(() => {
that.request(method,url,callback,postVars,true)
}, that.retryDelay*1000);
}
}
xhr.open(method,url,true);
if (method !== 'POST') postVars = null;
xhr.send(postVars)
}
}
QueueHandler.prototype.advanceQueue = function () {
if (this.queue.length !== 0) { // 如果暂存列表为空 结束本次函数
this.requestInProgress = true;
return;
}
var req = this.queue.shift(); // 如果不为空 发起暂存列表的第一个请求
this.request(req.method,req.url,req.callback,req.postVars,true)
}

// OfflineHandler calss
var OfflineHandler = function () {
this.storedRequests = [];
};
extend(OfflineHandler,SimpleHandler);
OfflineHandler.prototype.request = function (method,url,callback,postVars) {
if (XhrManager.isOffline()) { // XhrManager.isOffline方法判断是否离线
this.storedRequests.push({
method,
url,
callback,
postVars
})
}
else{
this.flusStoredRequests();
// 调用超类(也就是父类)的request方法
OfflineHandler.superclass.request(method,url,callback,postVars)
}
};
OfflineHandler.prototype.flusStoredRequests = function () {
for (let i = 0; i < storedRequests.length; i++) {
var req = storedRequests[i];
OfflineHandler.superclass.request(req.method,req.url,req.callback,req.postVars)
}
}
  • 在运行时选择连接对象(工厂模式)

    因为程序员根本不可能知道各个最终用户实际面临的网络条件,所以不可能要求他们在开发中选择使用那个处理器类,而是应该用一个工厂在运行时选择最合适的类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// XhrManager 单例
var XhrManager = {
createXhrHandler:function(){
var xhr;
if (this.isOffline()) {
xhr = new OfflineHandler();
}
else if (this.isHighLatency()) {
xhr = new QueueHandler();
}
else{
xhr = new SimpleHandler();
}
Interface.ensureImplements(xhr,AjaxHandler);
return xhr
},
isOffline:function(){
//... 判断是否离线
},
isHighLatency:function(){
//.. 判断是否高延迟
}
}
// 使用
var myHandler = XhrManager.createXhrHandler();
var callback = {
success:function(responseText){
// ...
},
failure:function(statusCode){
// ...
}
}
myHandler.request('GET','script.php',callback)

总结

简单工厂通常另外使用一个类或者对象封装实例化操作,而真正的工厂模式则需要实现一个抽象的工厂方法并把实例化工作推迟到子类中进行。
工厂模式可以弱化对象间的耦合,防止代码重复。