读书笔记之 - javascript 设计模式 - 工厂模式
一个类或者对象中,往往会包含别的对象。在创建这种对象的时候,你可能习惯于使用常规方式,即用 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 设计模式 - 工厂模式的更多相关文章
- 读书笔记之 - javascript 设计模式 - 命令模式
本章研究的是一种封装方法调用的方式.命令模式与普通函数有所不同.它可以用来对方法调用进行参数化处理和传送,经过这样处理过的方法调用可以在任何需要的时候执行. 它也可以用来消除调用操作的对象和实现操作的 ...
- 读书笔记之 - javascript 设计模式 - 代理模式
代理(proxy)是一个对象,它可以用来控制对另一对象的访问.它与另外那个对象实现了同样的接口,并且会把任何方法调用传递给那个对象.另外那个对象通常称为本体.代理可以代替本体被实例化,并使其可被远程访 ...
- 读书笔记之 - javascript 设计模式 - 门面模式
门面模式有俩个作用: 简化类的接口 消除类与使用它的客户代码之间的耦合 在javascript中,门面模式常常是开发人员最亲密的朋友.它是几乎所有javascript库的核心原则,门面模式可以使库提供 ...
- 读书笔记之 - javascript 设计模式 - 单体模式
单体是一个用来划分命名空间,并将一批相关方法和属性组织在一起的对象,如果它可以被实例化,那么它只能被实例化一次. 单体模式,就是将代码组织为一个逻辑单元,这个逻辑单元中的代码可以通过单一的变量进行访问 ...
- 读书笔记之 - javascript 设计模式 - 组合模式
组合模式是一种专为创建Web上的动态用户界面而量身定制的模式,使用这种模式,可以用一条命令在对各对象上激发复杂的或递归的行为. 在组合对象的层次体系中有俩种类型对象:叶对象和组合对象.这是一个递归定义 ...
- 读书笔记之 - javascript 设计模式 - 装饰者模式
本章讨论的是一种为对象增添特性的技术,它并不使用创建新子类这种手段. 装饰者模式可以透明地把对象包装在具有同样接口的另一对象之中,这样一来,你可以给一些方法添加一些行为,然后将方法调用传递给原始对象. ...
- javascript 设计模式-----工厂模式
所谓的工厂模式,顾名思义就是成批量地生产模式.它的核心作用也是和现实中的工厂一样利用重复的代码最大化地产生效益.在javascript中,它常常用来生产许许多多相同的实例对象,在代码上做到最大的利用. ...
- JavaScript设计模式——工厂模式
工厂模式:是一种实现“工厂”概念的面上对象设计模式.实质是定义一个创建对象的接口,但是让实现这个接口的类来决定实例化哪个类.工厂方法让类的实例化推迟到子类中进行.创建一个对象常常需要复杂的过程,所以不 ...
- javascript设计模式-工厂模式
简单工厂模式:使用一个类来生成实例. 复杂工厂模式:使用子类来决定一个成员变量应该是哪个具体的类的实例. 简单工厂模式是由一个方法来决定到底要创建哪个类的实例, 而这些实例经常都拥有相同的接口.通过工 ...
随机推荐
- IE-二级网页打不开
无法打开二级链接的处理方法是重新注册如下的DLL文件: 在开始—运行里输入: regsvr32 Shdocvw.dll regsvr32 Shell32.dll(注意这个命令,先不用输) regsvr ...
- [置顶] 漫谈SOA(面向服务架构)
面向服务架构的思想在整个软件的架构中已经不是什么新鲜的东西.我简单的认为服务化是模块化的延伸,所以服务化有着和模块化类似的优点和缺点.这里不再讨论这些服务定义服务与服务之间的通信协议(像WSDL等等) ...
- Spring源码入门——DefaultBeanNameGenerator解析
我们知道在spring中每个bean都要有一个id或者name标示每个唯一的bean,在xml中定义一个bean可以指定其id和name值,但那些没有指定的,或者注解的spring的beanname怎 ...
- (二)如何在.net中使用Redis
Step1:使用NuGet工具安装Redis C# API,这里有多个API我们可以使用其中一个:
- Filter案例
1.有选择的被访问 描述:首先若用户没有在页面提交注册(直接访问list.jsp),就只能被允许访问a.jsp.其他页面均不被允许访问 在login.jsp提交信息之后,可以在b.jsp访问, 代码如 ...
- Callgrind 使用 2
Callgrind是一款和gprof类似的性能分析工具,与gprof不同的是它不需要在编译源码时附加特殊选项,但推荐加上调试选项.Callgrind使用cachegrind的统计信息Ir(I cach ...
- Ubuntu远程桌面xrdp方法
xrdp方式,该方法支持多用户登录并远程桌面. 首先在Ubuntu上安装GNOME界面,在终端输入命令: sudo apt-get install gnome-panel 安装完成后注销用户. 点击自 ...
- asp.net分页控件库
AspNetPager分页控件 AspNetPager分页控件解决了分页中的很多问题,直接采用该控件进行分页处理,会将繁琐的分页工作变得简单化,下面是我如何使用AspNetPager控件进行分页处理的 ...
- air2调用本地exe的文章
流传了两种配置app.xml的方法,分别是: <supportedProfiles>extendedDesktop</supportedProfiles> <suppo ...
- flash wmode="window&qu…
引用一段话:opaque和tranparent由于都是无窗口内渲染模式,能很好的实现各层的遮挡,和一般div元素没有太大区别,而window.direct模式在现在看来,是没有任何办法实现被DHTML ...