代理模式
代理是一个对象,它可以用来控制对另一个对象的访问。它与另外那个对象实现了同样的接口,并且会把任何方法调用传递给那个对象。另外那个对象通常称为本体。代理可以代替其本体被实例化,并使其可被远程访问。它还可以把本体的实例化推迟到真正需要的时候,对于实例化比较费时的本体,或者因尺寸较大以至于不用时不宜保存在内存中的本体,这特别有用。在处理那些需要较长时间才能把数据载入用用户界面的类时,代理也大有裨益。
代理结构
代理模式最基本的形式是对访问进行控制。代理对象和另一个对象(本体)实现的是同样的接口。实际上工作还是本体在做。它才是负责执行所分派的任务的那个对象或类。代理对象所做的不外乎节制对本体的访问。要注意,代理对象并不会在另一个对象的基础上添加方法或修改其方法(就像装饰那样),也不会简化那个对象的接口(就像门面元素那样)。它实现的接口与本体完全相同。所有对它进行的方法调用都会被传递给本体。
代理如何控制对本体的访问
那种根本不实现任何访问控制的代理最简单。它所做的只是把所有方法调用传递到本体。这种代理离无用处,但它也可提供一个进一步发展的基础。
var Publication =new Interface(“Publication”,[‘getIsbn’,’setIsbn’,’getTitle’,’setTitle’,’getAuthor’,’setAuthor’,’display’]);
var Book =function(isbn,title,author){…} //implements Publication
//Libraryinterface
var Library = new Interface(“Library”,[“findBooks”,”checkoutBook”,”returnBook”]);
/*PublicLibraryclass*/
var PublicLibrary= function(books){
this.catalog = {};
for(var i=0,len=books.length;i<len;i++){
this.catalog[books[i].getIsbn()] ={book:book[i],available:true};
}
}
PublicLibrary.prototype={
findBooks:function(searchString){
var results=[];
for(var isbn in this.catalog){
if(!this.catalog.hasOwnProperty(isbn))continue;
if(searchString.match(this.catalog[isbn].getAuthor()) ||searchString.match(this.catalog[isbn].getTitle())){
results.push(this.catalog[isbn]);
}
}
return results;
},
checkoutBook:function(book){
var isbn = book.getIsbn();
if(this.catalog[isbn]){
if(this.catalog[isbn].available){
this.catalog[isbn].available=false;
return this.catalog[isbn];
}else{
throw new Error("PublicLibrary: book"+book.getTitle()+ " is not currentlyavailable.");
}
}
},
returnBook:function(boo){
var isbn = book.getIsbn();
if(this.catalog[isbn]){
this.catalog[isbn].available=true;
}else{
throw new Erro("PublicLibrary:book"+book.getTitle()+" not found.");
}
}
}
这个类非常简单。它可以用来查书、借书和还书。下面是一个没有实现任何访问控制的PublicLibrary类的代理:
/*PublicLibraryProxyclass , a useless proxy*/
var PublicLibraryProxy = function(catalog){
this.library = new PublicLibrary(catalog);
};
PublicLibraryProxy.prototype= {
findBooks:function(searchString){
return this.library.findBooks(searchString);
},
checkoutBook:function(book){
return this.library.checkoutBook(book);
},
returnBook:function(book){
return this.library.returnBook(book);
}
}
前面已经说过,这种类型的代理没有什么用处。在各种类型的代理中,虚拟代理最有用的类型之一。虚拟代理用于控制对那种创建开销很大的本体的访问。它会把本体的实例化推迟到有方法被调用的时候,有时还会提供关于实例化状态的反馈。它还可以在本体被加载之前扮演其替身的角色。作为一个例子,假设PublicLibrary的实例化很慢,不能在网页加载的时候立即完成。我们可以为其创建一个虚拟代理,让它把PublicLibrary的实例化推迟到必要的时候
/*PublicLibraryVirtualProxyclass*/
var PublicLibraryVirtualProxy= function(catalog){
this.library = null;
this.catalog = catalog;
}
PublicLibraryVirtualProxy.prototype= {
_initializeLibrary:function(){
if(this.library===null){
if(this.library ===null){
this.library = new PublicLibrary(this.catalog);
}
},
findBooks:function(searchString){
this._ initializeLibrary();
returnthis.library.findBooks(searchString);
},
checkoutBook:function(book){
this._ initializeLibrary();
returnthis.library.checkoutBook(book);
},
returnBook:function(book){
this._ initializeLibrary();
returnthis.library.returnBook(book);
}
}
}
PublicLibraryProxy和PublicLibraryVirtualProxy之间的关键区别在于后者不会立即创建PublicLibrary实例。PublicLibraryVirtualProxy会把构造函数的参数保存起来,直到有方法被调用才真正执行本体实例化。这样一来,如果图书馆对象一直未被用到,那么它就不会被创建出来。虚拟代理通常具有某种能触发本体的实例化的事件。
代理模式和装饰者模式的比较
代理有许多方面都很像装饰者。装饰者和虚拟代理都要对其他对象进行包装,都要实现与被包装对象相同的接口,而且都要把方法调用传递给被包装对象。那么二者的区别:最大的区别在于装饰者会对被包装对象的功能进行修改或扩充,而代理只不过是控制对它的访问。除了有时可能会添加一些控制代码之外,代理并不会对传递给本体的方法调用进行修改。而装饰者就是为修改方法而生的。另一个区别表现在被包装的对象的创建方式上。在装饰者模式中,被包装对象的实例化过程是完全独立的。这个对象创建之后,你可随意为其裹上一个或更多装饰者。而在代理模式中,被包装对象的实例华是代理实例化过程的一部分。在某些类型的虚拟代理中,这种实例化受到严格控制,它必须在代理内部进行。此外,代理不会像装饰者那样互相包装。它们一次只使用一个。
包装Web服务的通用模式
/*WebserviceProxyclass*/
var WebsrviceProxy= function(){
this.xhrHandler =XhrManager.createXhrHandler();
};
WebsrviceProxy.prototype= {
_xhrFailure:function(statusCode){
throw newError(“StatsProxy:Asynchronous request for stats failed.”);
},
_fetchData:function(url,dataCallback,getVars){
varthat = this;
varcallback={
success:function(responseText){
var obj = eval(“(“+responseText+”)”);
dataCallback(obj);
},
failure:that._xhrFailure
};
vargetVarArray = [];
for(varNamein getVars){
getVarArray.push(varName+”=”+getVars[varName]);
}
if(getVarArray.length>0){
url =url+”?”+getVarArray.join(“&”);
}
xhrHandler.request(“GET”,url,callback);
}
}
使用这个通用模式时,只需要从WebserviceProxy派生一个子类,然后再借助_fetchData方法实现需要的方法即可。如果把StatsProxy类实现为WebserviceProxy的子类,其结果大致如下:
/*StatsProxy class*/
var StatsProxy = function(){};
extend(statsProxy,WebserviceProxy);
StatsProxy.prototype.getPageviews= function(callback, startDate,endDate,page){
this._fetchData(‘/stats/getPageviews/’,callback,{
‘startDate’:startDate,
‘endDate’:endDate,
‘page’:page
});
}