一个类或者对象中,往往会包含别的对象。在创建这种对象的时候,你可能习惯于使用常规方式,即用 new 关键字和类构造函数。

这会导致相关的俩个类之间产生依赖。

工厂模式,就是消除这俩个类之间的依赖性的一种模式,它使用一种方法来决定究竟实例化那个具体的类。

简单工厂模式

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

var BicycleShop = function(){}
BicycleShop.prototype = {
sellBicycle:function(model){
var bicycle;
switvh(model){
case 'The Speedster':
bicycle = new Speedster();
break;
case 'The Lowrider':
bicycle = new Lowrider();
break;
case 'The Comfort Cruiser':
default:
bicycle = new ComfortCruiser();
}
Interface.ensureImplements(bicycle,Bicycle);
bicycle.assemble();
bicycle.wash();
return bicycle;
}
}

sellBicycle 方法根据所要求的自行车型号用 switch 来创建实例。

Bicycle接口:

var Bicycle = new Interface('Bicycle',['assemble','wash','ride','repair']); 

Speedster 类:

var Speedster = function(){
...
};
Speedster.prototype = {
assemble:fucntion(){
    ...
  },
  wash:fucntion(){
    ...
  },
  ride:fucntion(){
    ...
  },
  repair:fucntion(){
    ...
  }
} var californiaCruisers = new BicycleShop();
var yourNewBike = californiaCruisers.sellBicycle('The Speedster');

如果想加入一款新型号的车,只能修改BicycleShop的代码了。最好的办法就是把sellBicycle方法中“创建新实例”这部分工作转交给一个简单工厂对象:

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();
}
Interface.ensureImplements(bicycle,Bicycle);
return bicycle;
}
}

BicycleFactory是一个单体,用来把createBicycle封装在一个命名空间中,这个方法返回一个实现了Bicycle接口的对象。然后你可以照常对其进行组装和清洗。

var BicycleShop = function(){}
BicycleShop.prototype = {
sellBicycle:function(model){
var bicycle = BicycleFactory.createBicycle(model);
bicycle.assemble();
bicycle.wash();
return bicycle;
}
}

这样,有关提供车型的所有信息都集中到一个地方管理,所以添加更多车型很容易。

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

真正的工厂模式

真正的工厂模式与简单的工厂模式区别在于,它不是另外使用一个类或者对象来创建自行车,而是使用一个子类。

按照正常定义,工厂是一个将其成员对象的实例化推迟到子类中进行的类。

我们打算让自行车商店自己决定从那个生产厂家进货。出于这个原因,单单一个BicycleFactory对象将无法提供需要的所有自行车实例。我们可以把BicycleShop设计为抽象类,然后让子类根据各自的进货渠道实现其createBicycle方法:

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.');
}
}

以上类中定义了createBicycle方法,但是调用这个方法,会抛出一个错误,现在BicycleShop是一个抽象类,它不能被实例化,只能用来派生子类。

设计一个经销特定自行车生产厂家产品的子类需要扩展 BicycleShop 方法,下面是扩展方法。

var AcmeBicycleShop = function(){};
extend(AcmeBicycleShop,BicycleShop);
AcmeBicycleShop.prototype.createBicycle = function(model){
var bicycle;
switch(model){
case 'The Speedster':
bicycle = new AcmSpeedster();
break;
case 'The Lowrider':
bicycle = new AcmLowrider();
break;
case 'The Comfort Cruiser':
default:
bicycle = new AcmComfortCruiser();
}
Interface.ensureImplements(bicycle,Bicycle);
return bicycle;
} var alecsCruisers = new AcmeBicycleShop();
var yourNewBike = alecsCruisers.sellBicycle('The Lowrider');

对Bicycle进行的一般性操作的代码完全可以放在父类BicycleShop中,而对具体的Bicycle对象进行实例化的工作则被留到子类中。

这样,一般性的代码被集中在一个位置,而个体性的代码则被封装在子类中。

如果需要像前面那样,创建一些用不同方法实现同一接口的对象,可以使用简单工厂方法来简化-选择实现-的过程。这种选择可以是明确的,也可以是隐含的,明确如自行车例子,隐含的如下面的HXR实现:

在有些场合下,你通常要与一系列实现了同一接口,可以被同等对待的类打交道,这是js中使用工厂模式最常见的原因。

XHR 工厂实例:

用于发起请求的对象是某种类的实例,具体是哪种类取决于用户的浏览器。如果代码中需要多次执行Ajax请求,那么明智的做法就是把创建这种对象提取到一个类中,并创建一个包装器来包装在实际发起请求时所要经历的一系列步骤。简单工厂非常适合这种场合,它可以根据浏览器能力的不同生成一个XMLHttpRequest或 ActiveXObject 实例。

var AjaxHandler = new Interface('AjaxHandler',['request','createXhrObject']);

var SimpleHandler = function(){};
SimpleHandler.prototype = {
request:function(method,url,callback,postVars){
var xhr = this.createXhrObject();
xhr.onreadystatechange = function(){
if(xhr.readyState!==4) return;
(xhr.status===200)?
callback.success(xhr.responseText,xhr.responseXML):
callback.failure(xhr.status);
}
xhr.open(method,url,true);
if(method!=='POST') postVars=null;
xhr.send(postVars);
},
createXhrObject:function(){
var methods = [
function(){ return new XMLHttpRequest();},
function(){ return new ActiveXObject('Msxml2.XMLHTTP');},
function(){ return new ActiveXObject('Microsoft.XMLHTTP');}
]; for(var i=0,len=methods.length;i<len;i++){
try{
methods[i]();
}catch(e){
continue;
}
//这里比较有意思,是一种记忆方式,代码执行一次之后,createXhrObject函数改变。
this.createXhrObject = methods[i];
return methods[i];
}
throw new Error('SimpleHandler:could not create xhr object');
}
}

上面这例子可以进一步扩展,把工厂模式用在俩个地方,以便根据网络条件创建专门的请求对象。在创建XHR对象的时候已经使用过了简单工厂模式。另一个工厂则用来返回各种处理器类,他们都派生自SimpleHandler。

首先要做的是创建俩个新的处理器类

QueueHandler 会在发起新请求之前确保所有的请求都成功处理。

OfflineHandler 则会在用户处于离线状态是把请求缓存起来。

var QueuedHandler = function(){
this.queue = [];
this.requestInProgress = false;
this.retryDelay = 5;
} extend(QueuedHandler,SimpleHandler); QueuedHandler.prototype.request = function(method,url,callback,postVars,override){
//如果前面的请求还在处理,则直接把这些参数推入数组
if(this.requestInProgress && !override){
this.queue.push({
method:method,
url:url,
callback:callback,
postVars:postVars
});
}else{
//如果处理完成,则执行该操作
//把状态设置为处理中。。。。
//创建对象
this.requestInProgress = true;
var xhr = this.createXhrObject();
var _this = this;
xhr.onreadystatechange = function(){
if(xhr.readyState!==4) return; if(xhr.status===200){
//请求成功,执行callback函数
callback.success(xhr.responseText,xhr.responseXML);
//继续处理队列中的请求
_this.advanceQueue();
}else{
//请求失败,则每隔5秒进行一次请求
callback.failure(xhr.status);
setTimeout(function(){
_this.request(method,url,callback,postVars,override);
},_this.retryDelay*1000);
};
};
xhr.open(method,url,true);
if(method!=='POST') postVars=null;
xhr.send(postVars);
}
}; QueuedHandler.prototype.advanceQueue = function(){
if(this.queue.length===0){
this.requestInProgress = false;
return;
}
var req = this.queue.shift();
this.request(req.method,req.url,req.callback,req.postVars,true);
}

QueuedHandler 的 request 方法与SimpleHandler的看上去差不多,但是允许发起新的请求之前先检查一下,以确保当前没有别的请求正在处理。

OfflineHandler要更简单一点:

var OfflineHandler = function(){
this.storedRequests = [];
}
extend(OfflineHandler,SimpleHandler);
OfflineHandler.prototype.request = function(method,url,callback,postVars){
if(xhrManager.isOffline()){
this.storedRequests.push({
method:method,
url:url,
callback:callback,
postVars:postVars
});
}else{
this.flushStoredRequests();
OfflineHandler.superclass.request(method,url,callback,postVars);
};
OfflineHandler.prototype.flushStoredRequests = function(){
for(var i=0,len=storedRequests.length;i<len;i++){
var req = storedRequests[i];
OfflineHandler.superclass.request(req.method,req.url,req.callback,req.postVars);
}
}
}

xhrManager.isOffline 方法的作用在于判断用户是否处于在线状态。

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

var xhrManager = {
createXhrHandler:function(){
var xhr;
if (this.isOffline()) {
xhr = new offlineHandler();
}else if(this.isHighLatency()){
xhr = new QueuedHandler();
}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);

示例:RSS阅读器:

RSS 阅读器对象,它的成员对象包括一个XHR处理器对象,一个显示对象,一个配置对象,XHR处理器类我们使用上面的xhrManager.createXhrHandler方法所创建的处理器对象,下面是一个显示类(显示对象):

var DisplayModule = new Interface('DisplayModule',['append','remove','clear']);

var ListDisplay = function(id,parent){
this.list = document.createElement('ul');
this.list.id = id;
parent.appendChild(this.list);
}
ListDisplay.prototype = {
append:function(text){
var newEl = document.createElement('li');
this.list.appendChild(newEl);
newEl.innerHTML = text;
return newEl;
},
remove:function(el){
this.list.removeChild(el);
},
clear:function(){
this.list.innerHTML = '';
}
}

下面是一个配置对象,这只是一个对象字面量,包含一些供阅读器以及成员对象使用的设置:

var conf = {
id:'cnn-top-stories',
feedUrl:'http://.....rss',
updateInterval:60,
parent:$('feed-readers')
}

这些类由FeedReader组合使用。它使用XHR获取数据,并解析,最后显示模块将信息输出到网页:

var FeedReader = function(display,xhrHandler,conf){
this.display = display;
this.xhrHandler = xhrHandler;
this.conf = conf;
this.startUpdates();
}
FeedReader.prototype = {
fetchFeed:function(){
var _this = this;
var callback = {
success:function(text,xml){
_this.parseFeed(text,xml);
},
failure:function(status){
_this.showError(status);
}
};
this.xhrHandler.request('GET',this.conf.feedUrl,callback);
},
parseFeed:function(responseText,responseXML){
this.display.clear();
var items = responseXML.getElementsByTagName('item');
for(var i= 0,len=items.length;i<len;i++){
var title = items[i].getElementsByTagName('title')[0];
var link = items[i].getElementsByTagName('link')[0];
this.display.append('<a href="'+link.firstChild.data+'">'+title.firstChild.data+'</a>');
}
},
showError:function(status){
this.display.clear();
this.display.append('Error fetching feed.');
},
stopUpdates:function(){
clearInterval(this.interval);
},
startUpdates:function(){
this.fetchFeed();
var _this = this;
this.interval = setInterval(function(){_this.fetchFeed();},
this.conf.updateInterval*1000);
}
}

现在还差一个部分,即把所有这些类和对象拼装起来的那个工厂方法,他被实现为一个简单的工厂:

var FeedManager = {
createFeedReader:function(conf){
var displayModule = new ListDisplay(conf.id+'-display',conf.parent);
Interface.ensureImplements(displayModule,DisplayModule); var xhrHandler = xhrManager.createXhrHandler();
Interface.ensureImplements(xhrHandler,AjaxHandler);
return new FeedReader(displayModule,xhrHandler,conf);
}
}

使用API的程序员当然可以手工创建一个FeedReader对象,而不必借助FeedManager.createFeedReader方法,但是使用这个工厂方法,可以把FeedReader类所需要的复杂设置封装取来,并且可以确保其成员对象都实现了所需接口。

总结:

工厂模式主要用于消除对象间的耦合。通过使用工厂方法,而不是new关键字以及具体类,你可以把所有实例化代码集中在一个位置。

使用工厂模式,你可以先创建一个抽象的父类,然后在子类中创建工厂方法,从而把成员对象的实例化推迟到更专门化的子类中进行。

有人不禁把工厂方法当做万金油,把构造函数扔在一边。这并不值得提倡。

如果根本不可能另外换用一个类,或者不需要在运行期间在一系列可互换的类中进行选择,那就不应该使用工厂方法。

它主要用于所实例化的类型不能在开发期确定,而只能在运行期间确定的情况,此外,如果存在许多具有复杂的设置开销的相关对象,或者想创建一个包含了一些成员对象的类但是又想避免它们紧密的偶合在一起的话,就应该使用工厂模式。

读书笔记之 - javascript 设计模式 - 工厂模式的更多相关文章

  1. 读书笔记之 - javascript 设计模式 - 命令模式

    本章研究的是一种封装方法调用的方式.命令模式与普通函数有所不同.它可以用来对方法调用进行参数化处理和传送,经过这样处理过的方法调用可以在任何需要的时候执行. 它也可以用来消除调用操作的对象和实现操作的 ...

  2. 读书笔记之 - javascript 设计模式 - 代理模式

    代理(proxy)是一个对象,它可以用来控制对另一对象的访问.它与另外那个对象实现了同样的接口,并且会把任何方法调用传递给那个对象.另外那个对象通常称为本体.代理可以代替本体被实例化,并使其可被远程访 ...

  3. 读书笔记之 - javascript 设计模式 - 门面模式

    门面模式有俩个作用: 简化类的接口 消除类与使用它的客户代码之间的耦合 在javascript中,门面模式常常是开发人员最亲密的朋友.它是几乎所有javascript库的核心原则,门面模式可以使库提供 ...

  4. 读书笔记之 - javascript 设计模式 - 单体模式

    单体是一个用来划分命名空间,并将一批相关方法和属性组织在一起的对象,如果它可以被实例化,那么它只能被实例化一次. 单体模式,就是将代码组织为一个逻辑单元,这个逻辑单元中的代码可以通过单一的变量进行访问 ...

  5. 读书笔记之 - javascript 设计模式 - 组合模式

    组合模式是一种专为创建Web上的动态用户界面而量身定制的模式,使用这种模式,可以用一条命令在对各对象上激发复杂的或递归的行为. 在组合对象的层次体系中有俩种类型对象:叶对象和组合对象.这是一个递归定义 ...

  6. 读书笔记之 - javascript 设计模式 - 装饰者模式

    本章讨论的是一种为对象增添特性的技术,它并不使用创建新子类这种手段. 装饰者模式可以透明地把对象包装在具有同样接口的另一对象之中,这样一来,你可以给一些方法添加一些行为,然后将方法调用传递给原始对象. ...

  7. javascript 设计模式-----工厂模式

    所谓的工厂模式,顾名思义就是成批量地生产模式.它的核心作用也是和现实中的工厂一样利用重复的代码最大化地产生效益.在javascript中,它常常用来生产许许多多相同的实例对象,在代码上做到最大的利用. ...

  8. JavaScript设计模式——工厂模式

    工厂模式:是一种实现“工厂”概念的面上对象设计模式.实质是定义一个创建对象的接口,但是让实现这个接口的类来决定实例化哪个类.工厂方法让类的实例化推迟到子类中进行.创建一个对象常常需要复杂的过程,所以不 ...

  9. javascript设计模式-工厂模式

    简单工厂模式:使用一个类来生成实例. 复杂工厂模式:使用子类来决定一个成员变量应该是哪个具体的类的实例. 简单工厂模式是由一个方法来决定到底要创建哪个类的实例, 而这些实例经常都拥有相同的接口.通过工 ...

随机推荐

  1. 小波变换和motion信号处理(三)(转)

    这篇文章算太监了,去作者blog提问去吧:http://www.kunli.info/2012/04/08/fourier-wavelet-motion-signal-3/ 从前两篇发布到现在,过去一 ...

  2. Linux文件误删除恢复操作

    作为一个多用户.多任务的操作系统,Linux下的文件一旦被删除,是难以恢复的.尽管删除命令只是在文件节点中作删除标记,并不真正清除文件内容,但是 其他用户和一些有写盘动作的进程会很快覆盖这些数据.不过 ...

  3. IOS网络多线程-GCD

    Grand Central Dispatch (GCD)是Apple开发的一个多核编程的解决方法. dispatch queue分成以下三种: 1)运行在主线程的Main queue,通过dispat ...

  4. 【python自动化第十篇:】

    复习: 线程与进程的区别: 进程:资源的集合 线程:最小调度单位 进程至少包含一个线程 线程之间的内存是共享的,两个线程操作同一个数据就会修改整个结果(需要mutex加锁来保持数据的一致性),递归锁, ...

  5. JDK之jstat的用法

    http://www.51testing.com/html/92/77492-203728.html jstat的用法 用以判断JVM是否存在内存问题呢?如何判断JVM垃圾回收是否正常?一般的top指 ...

  6. ASM集群文件系统ACFS(ASM Cluster File System)

    在11g R2中ASM文件支持包括数据文件,控制文件,归档日志文件,spfile,RMAN备份文件,Change Tracking文件,数据泵Dump文件盒OCR文件等.而推出的ACFS和Oracle ...

  7. beaglebone-black 在Angstrom系统中的网络配置方法

    Beaglebone Linux 101: Assigning a Static IP Address with Connman Posted on February 6, 2012 by dwatt ...

  8. 认识copy关键

    首先先引用阳神Sunny博客中的一道面试题: 用@property声明的NSString(或NSArray,NSDictionary)经常使用copy关键字,为什么?如果改用strong关键字,可能造 ...

  9. 简论数据库乐观悲观锁与并发编程中的CAS

    为了防止无良网站的爬虫抓取文章,特此标识,转载请注明文章出处.LaplaceDemon/ShiJiaqi. http://www.cnblogs.com/shijiaqi1066/p/5783205. ...

  10. jQuery 序列化表单 serialize() serializeArray()

    1.serialize()方法 格式:var data = $("form").serialize(); 功能:将表单内容序列化成一个字符串. 这样在ajax提交表单数据时,就不用 ...