工厂模式
简单工厂
最好用一个例子来说明简单工厂模式的概念。假设你想开几个自行车商店,每个店都有几种型号的自行车出售。这可以用一个类来表示
/*BicycleShopclass*/
var BicycleShop =function(){};
BicycleShop.prototype= {
sellBicycle:function(model){
var bicycle;
switch(model){
case ‘The Speedster’:
bicycle = new Speedster();
break;
case ‘The Lowrider:
bicycle = new Lowrider();
break;
case ‘The Comfort Cruiser:
bicycle = new ComfortCruiser();
break;
}
Interface.ensureImplements(bicycle,Bicycle);
bicycle.assemble();
bicycle.wash();
return bicycle;
}
}
sellBicycle方法根据所要求的自行车型号用switch语句创建一个自行车的实例。各种型号自行车实例可以互换使用,因为它们都实现了Bicycle接口:
注意:接口在工厂模式中起着很重要的作用。如果不对对象进行某种类型检查以确保其实现了必需的方法,工厂模式带来的好处也就所剩无几了。在所有这些例子中,你可以创建一些对象并且它们一视同仁,那是因为你可以确信它们都实现了同样一批方法。
// The Bicycleinterface
var Bicycle = new Interface(“Bicycle”,[‘assemble’,’wash’,’ride’,’repair’]);
/*Speedster class.*/
var Speedster =function(){
…//实现Bicycle
}
Speedster.prototype= {
assemble:function(){},
wash:function(){},
ride:function(){},
repair:function(){}
};
要出售某种型号的自行车,只要调用sellBicycle方法即可:
var californiaCruisers = new BicycleShop();
var youNewBike = californiaCruisers.sellBicycle(‘TheSpeedster’);
在情况发生变化之前,这倒是也挺管用。但要是你想在供货目录中加入一款新车型又会怎么呢?你得为此修改BicycleShop的代码,哪怕这个类的实际功能实际上并没有发生改变依旧是创建一个自行车的新实例,组装它,清洗它,然后把它交给顾客。更好的解决办法是把把sellBicycle中的“创建新实例”这部分工作转交给一个简单工厂对象:
/*BicycleFactorynamespace.*/
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:
bicycle = new ComfortCruiser()
break;
}
Interface.ensureImplements(bicycle,Bicycle);
return bicycle;
}
}
BicycleFactory是一个单体,用来把createBicycle方法封装在一个命名空间中。这个方法返回一个实现了Bicycle接口的对象,然后你可以照常对其进行组装和清洗:
/*BicycleShopclass , improved.*/
var BicycleShop =function(){};
BicycleShop.prototype= {
sellBicycle:function(model){
var bicycle =BicycleFactory.createBicycle(model);
bicycle.assemble();
bicycle.wash();
return bicycle;
}
}
这个BicycleFactory对象可以供各种类用来创建新的自行车实例。有关可供车型的所有信息都集中在一个地方管理,所以添加更多车型很容易:
以上就是一个很好的例子。这种模式把成员对象的创建工作转交给一个外部对象。这个外部对象可以像本例中一样是一个简单的命名空间,也可以是一个类的实例。如果负责创建实例的方法的逻辑不会发生变化,那么一般说来用单体或静态类方法创建这些成员实例是合乎情理的。但如果你要提供几种不同品牌的自行车,那么更恰当的做法是把这个创建方法实现在一个类中,并从该类派生出一些子类。
工厂模式
真正的工厂模式与简单工厂模式的区别在于,它不是另外用一个类或对象来创建自行车,而是使用一个子类。按照正式定义,工厂是一个将其成员对象的实例化推迟到子类中进行的类。我们还是以BicycleShop为例来说明简单工厂和工厂模式之间的差别。
我们打算让各个自行车商店自行决定从哪个生产厂家进货。出于这个原因,单单一个BicycleFactory对象将无法提供需要的所有自行车实例。我们可以把BicycleShop设计为抽象类,让子类根据各自的进货渠道来实现其createBicycle方法:
var BicycleShop =function(){};
BicycleShop.prototype= {
sellBicycle:function(model){
var bicycle = this.createBicycle(model);
bicycle.assemble();
bicycle.wash();
return bicycle;
},
createBicycle:fucntion(model){
throw new Error(“Unsupport operation on an abatract class”);
}
}
这个类定义了createBicycle方法,但真要调用这个方法的话,会抛出一个错误。现在BicycleShope是一个抽象类,它不能被实例化,只能用来派生子类。设计一个经销特定自行车生产厂家产品的子类需要扩展BicycleShop,重定义其中的createBicycle方法。(好像有点java中的抽像工厂)
下面是两个子类的例子
/*AcmeBicycleShopclass*/
var AcmeBicycleShop = function(){};
extend(AcmeBicycleShop,BicycleShop);
AcmeBicycleShop.prototype.createBicycle=function(model){
var bicycle;
switch(model){
case ‘The Speedster’:
bicycle = new AcmeSpeedster()
break;
case ‘The Lowrider:
bicycle = new AcmeLowrider()
break;
case ‘The Comfort Cruiser:
bicycle = new AcmeComfortCruiser()
break;
default:
bicycle = newAcmeComfortCruiser();
}
Interface.ensureImplements(bicycle,Bicycle);
return bicycle;
}
/*GeneralProductsBicycleShopclass*/
var GeneralProductsBicycleShop= function(){};
extend(GeneralProductsBicycleShop,BicycleShop);
GeneralProductsBicycleShop.prototype.createBicycle=function(model){
var bicycle;
switch(model){
case ‘The Speedster’:
bicycle = new GeneralProductsSpeedster()
break;
case ‘The Lowrider:
bicycle = new GeneralProductsLowrider()
break;
case ‘The Comfort Cruiser:
bicycle = new GeneralProductsComfortCruiser()
break;
default:
bicycle = new GeneralProductsComfortCruiser();
}
Interface.ensureImplements(bicycle,Bicycle);
return bicycle;
}
这些工厂生成的对象都实现了Bicycle接口,所以在其他代码眼里它们完全可以互换。
示例:XHR工厂
用Ajax技术发起异步请求是现在web甘托克的一个常见任务。用于发起请求的对象是某种类的实例,具体是哪种类取决于用户的浏览器。哪果代码中需要多次执行Ajax请求,那么明智的做法是把创建这种对象的代码提取到一个类中,并创建一个包装器来包装在实际发起请求时所要经历的一系列步骤。简单工厂非常适合这处场合,它可以用来根据浏览器能力的不同生成一个XMLHttpRequest或ActiveXObject实例。
/*AjaxHandlerinterface.*/
var AjaxHandler =new Interface(“AjaxHandler”,[“request”,”createXhrObject”]);
/*SimpleHandlerclass.*/
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(){
//factory method
var methods = [
function(){return newXMLHttpRequest();},
function(){return newActiveXObjct(“Msxml2.XMLHTTP”);},
function(){return new ActiveXObject(“Microsoft.XMLHTTP”);}
];
for(vari=0,len=methods.length;i<len;i++){
try{
methods[i]();
}catch(e){
continue;
}
this.createXhrObject= method[i];
return methods[i];
}
throw new Error(‘SimpleHandler:Couldnot crete an XHR object.’);
}
}
用SimpleHandler类发起异步请求的过程很简单,只要创建该类的一个实例,调用它的request方法即可:
var myHandler= new SimpleHandler();
var callback = {
success:function(responseText){alert(“Success”+responseText);},
failure:function(statusCode){alert(“Failure:”+statusCode);}
}
myHandler.request(“GET”,’script.php’,callback);
专用型连接对象
这个例子可以进一步扩展,把工厂模式用在两个地方,以便根据网络条件创建专门的请求对象。在创建XHR对象时已经用过了简单工厂模式。另一个工厂则用来返各种处理器类,它们都派生自SimpleHandler。
首先要做的是创建两个新的处理器类。QueuedHandler会在发起新的请求之前先确保所有请求都已经成功处理。而OfflineHandler则会在用户处于离线状态时把请求缓存起来
/*QueuedHandlerclass.*/
var QueuedHandler= function(){
//implements ajaxhandler
this.queue = [];
this.requestInProgress = false;
this.retryDelay = 5;
}
extned(QueuedHandler,SimpleHandler);
QueuedHandler.prototype.request= function(method,url,callback,postVars,override){
if(this.requestInProgress && !override){
this.queue.push({
method:method,
calback:callback,
postVars: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.responseText,xhr.responseXML);
that.advanceQueue();
}else{
callback.failure(xhr.status);
setTimeout(function(){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 = false;
return ;
}
var req = this.queue.shift();
this.request(req.method,req.url,req.callback,req.postVars,true);
}
上例在发起请求之前它会先检查一下,以确保当前没有别的请求正在处理。如果有哪个请求未能成功处理,那么它还会在指定的时间间隔后再次重复这个请求,直到该请求被成功处理为止。
OffineHandler要简单一点:
/*OfflineHandlerclass*/
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.flushStoreRequests= function(){
for(var i=0,len=storeRequests.length;i<len;i++){
var req = storedRequests[i];
OfflineHandler.superclass.request(req.method,req.url,req.callback,req.postVars);
}
}
在运行时选择连接对象
现在该用到工厂模式了。因为程序员根本不可能知道各个最终用户实际面临的网络条件,所以不可能要求他们在开发过程中选择使用哪个处理器类,而是应该用一个工厂运行时选择最合适的类。程序员只需要调用这个工厂方法并使其返回的对象即。因为所有这些处理器类都实现了AjaxHandler接口,所以它们可以被同等对待。接口是相同的,区别在于其实现
/*XhrMangersingleton.*/
var XhrManger = {
createXhrHandler:funtion(){
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(){
//Do a quick request with SimpleHandlerand see if it succeeds.
},
isHighLatency:function(){
//Do a series of requests withSimpleHandler and time the responses. Best done once, as a branching function.
}
}