一、定义

  代理是一个对象,它可以用来控制对另一个对象的访问。它与另外那个对象实现了同样的接口,并且会把任何方法调用传递给那个对象。另外那个对象通常称为本体。代理可以代替其实体被实例化,并使其可被远程访问。它还可以把本体的实例化推迟到真正需要的时候,对于实例化比较费时的本体,或者因尺寸较大以至于不用时不易保存在内存中的本体,这特别有用。在处理那些需要较长时间才能把数据载入用户界面的类时,代理也大有裨益。

  代理模式最基本的形式是对访问进行控制。代理对象和另一个对象(本体)实现的是同样的接口。实际上工作还是本体在做,它才是负责执行所分派的任务的那个对象或类。代理对象所做的不外乎节制对本体的访问。要注意,代理对象并不会在另一对象的基础上添加方法或修改方法(就像装饰者那样),也不会简化那个对象的接口(就像门面元素那样)。它实现的接口与本体完全相同,所有对它进行的方法调用都会被传递给本体。

二、代理如何控制对本体的访问

  2.1 直接代理

var Library = new Interface('Library', ['findBooks', 'checkoutBook', 'returnBook']);

var PublicLibrary = function(books) { // implements Library
this.catalog = {};
for(var i = 0, len = books.length; i < len; i++) {
this.catalog[books[i].getIsbn()] = { book: books[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].getTitle()) ||
searchString.match(this.catalog[isbn].getAuthor())) {
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 currently available.');
}
}
else {
throw new Error('PublicLibrary: book ' + book.getTitle() + ' not found.');
}
},
returnBook: function(book) {
var isbn = book.getIsbn();
if(this.catalog[isbn]) {
this.catalog[isbn].available = true;
}
else {
throw new Error('PublicLibrary: book ' + book.getTitle() + ' not found.');
}
}
};
var PublicLibraryProxy = function(catalog) { // implements Library
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);
}
};

  PublicLibraryProxy和PublicLibrary实现了同样的接口和同一批方法。这个类在实例化时会创建一个PublicLibrary实例并将其作为属性保存。如果调用该类的某个方法,它会通过这个属性在其PublicLibrary实例上调用同名方法。这种类型的代理也可以通过检查本体的接口并为每一个方法创建对应方法这样一种方式动态地创建。

  2.2 虚拟代理

var PublicLibraryVirtualProxy = function(catalog) { // implements Library
this.library = null;
this.catalog = catalog; // Store the argument to the constructor.
};
PublicLibraryVirtualProxy.prototype = {
_initializeLibrary: function() {
if(this.library === null) {
this.library = new PublicLibrary(this.catalog);
}
},
findBooks: function(searchString) {
this._initializeLibrary();
return this.library.findBooks(searchString);
},
checkoutBook: function(book) {
this._initializeLibrary();
return this.library.checkoutBook(book);
},
returnBook: function(book) {
this._initializeLibrary();
return this.library.returnBook(book);
}
};

  PublicLibraryVirtualProxy会把构造函数的参数保存起来,直到有方法被调用时才真正执行本体的实例化。这样一来,如果图书馆对象一直没有被调用到,那么它就不会被创建出来。虚拟代理通常具有某种能触发本体的实例化的事件。在本例中,方法调用就是触发元素。

三、与装饰者模式的区别

  共同点:两者都要对其他对象进行包装,都要事先与被包装对象相同的接口,而且都要把方法调用传递给被包装对象。
  不同点:
  1> 装饰者会对被包装对象的功能进行修改或扩充,而代理只不过是控制对它的访问。除了有时可能会添加一些控制代码之外,代理并不会对传递给本体的方法调用进行修改。而装饰者就是为修改方法而生的。
  2> 在装饰者模式中,被包装对象的实例化过程是完全独立的。这个对象创建出来之后,你可以随意为其包裹上一个或更多装饰者。而在代理模式中,被包装对象的实例化时代理的实例化过程的一部分。在某些类型的虚拟代理中,这种实例化受到严格控制,它必须在代理内部进行。
  3> 代理不会像装饰者那样相互包装。它们一次只使用一个。

四、使用场合

  虚拟代理是一个对象,用于控制对一个创建开销昂贵的资源的访问。虚拟代理是一种优化模式。如果有些对象需要使用大量内存保存其数据,而你并不需要在实例化完成之后访问这些数据,或者,其构造函数需要进行大量计算那就应该使用虚拟代理将设置开销的产生推迟到真正需要使用数据的时候。代理可以在设置的进行过程中提供类似于“正在加载.....”这样的消息,这可以形成一个反应积极的用户界面,以免让用户面对一个没有任何反馈的空白页面发呆,不知道究竟发生了什么事。

  远程代理则没有这样清楚的用例。如果需要访问某种远程资源的话,那么最好是用一个类或独享来包装它,而不是一遍又一遍地手工设置XMLHttpRequest对象。问题在于应该用什么类型的对象来包装这个资源呢?这主要是个命名问题。如果包装对象实现了远程资源的所有方法,那么它就是一个远程代理。如果它会在运行期间增添一些方法,那它就是一个装饰者。如果它简化了该远程资源(或多个远程资源)的接口,那它就是一个门面。远程代理是一种结构型模式,它提供了一个访问位于其他环境中的资源的原生JavaScript API。

  下面引入包装web服务的通用包装模式的代理——

var WebserviceProxy = function() {
this.xhrHandler = XhrManager.createXhrHandler();
};
WebserviceProxy.prototype = {
_xhrFailure: function(statusCode) {
throw new Error('StatsProxy: Asynchronous request for stats failed.');
},
_fetchData: function(url, dataCallback, getVars) {
var that = this;
var callback = {
success: function(responseText) {
var obj = eval('(' + responseText + ')');
dataCallback(obj);
},
failure: that._xhrFailure
}; var getVarArray = [];
for(varName in getVars) {
getVarArray.push(varName + '=' + getVars[varName]);
}
if(getVarArray.length > 0) {
url = url + '?' + getVarArray.join('&');
} xhrHandler.request('GET', url, callback);
}
}; /* StatsProxy class, using WebserviceProxy. */ var StatsProxy = function() {}; // implements PageStats
extend(StatsProxy, WebserviceProxy); /* Implement the needed methods. */ StatsProxy.prototype.getPageviews = function(callback, startDate, endDate, page) {
this._fetchData('/stats/getPageviews/', callback, {
'startDate': startDate,
'endDate': endDate,
'page': page
});
};
StatsProxy.prototype.getUniques = function(callback, startDate, endDate, page) {
this._fetchData('/stats/getUniques/', callback, {
'startDate': startDate,
'endDate': endDate,
'page': page
});
};
StatsProxy.prototype.getBrowserShare = function(callback, startDate, endDate, page) {
this._fetchData('/stats/getBrowserShare/', callback, {
'startDate': startDate,
'endDate': endDate,
'page': page
});
};
StatsProxy.prototype.getTopSearchTerms = function(callback, startDate, endDate, page) {
this._fetchData('/stats/getTopSearchTerms/', callback, {
'startDate': startDate,
'endDate': endDate,
'page': page
});
};
StatsProxy.prototype.getMostVisitedPages = function(callback, startDate,endDate) {
this._fetchData('/stats/getMostVisitedPages/', callback, {
'startDate': startDate,
'endDate': endDate
});
};

五、优势

  远程代理的好处在于,可以把远程资源当做本地JavaScript对象使用。它减少了为远程访问资源而不得不编写的粘合性代码的数量,并且为此提供了单一的接口。如果远程资源提供的API发生了改变,需要修改的代码只有一处。它还把与远程资源相关的所有数据统一保存在一个地方,其中包括资源的URL、数据格式、命令和相应的结构。如果需要访问多个WEB服务,那么可以先创建一个抽象的通用远程代理类,然后针对每一种要访问的web服务派生出一个子类。

  虚拟代理的好处在于:可以把大对象的实例化推迟到其他元素加载完毕之后。如果虚拟代理包装的资源没有被用到,那么根本就不会被加载。不用操心实例化开销的问题。

六、劣势

  代理可以掩盖了大量复杂行为。

  对于远程代理而言,其背后的复杂行为包括发出XHR请求、等待响应、对响应接口进行解析以及输出收到的数据。在使用远程代理的程序员眼里,它可能就像一个本地资源,但访问它花的时间却比访问本地资源要出几个数量级。而且,它需要和毁掉函数结合使用,因为让方法直接放回结果是行不通的,这给代码增加了一定的复杂性,并且进一步拆穿了其访问资源的假象。这里的问题也能通过精心编撰的程序文档来消除(至少也可以减轻其不利影响)。

  对于虚拟代理也是如此。它掩盖了推迟本体的实例化的逻辑。使用这种代理的程序员并不清楚又那些操作会触发对象的实例化。
  综上,因为代理与其本体完全可以互换,如果没有令人信服的理由使用代理的话(或者降低代码的冗余程度,或者提高其模块化的程度,或运行效率),最好还是选择直接访问本体这种简单得多的方法。

源自:JavaScript设计模式(人民邮电出版社)——第十二章,装饰者模式

【读书笔记】读《JavaScript设计模式》之代理模式的更多相关文章

  1. 读书笔记之 - javascript 设计模式 - 享元模式

    本章探讨另一种优化模式-享元模式,它最适合于解决因创建大量类似对象而累及性能的问题.这种模式在javascript中尤其有用,因为复杂的javascript代码很快就会用光浏览器的所有可用内存,通过把 ...

  2. 读书笔记之 - javascript 设计模式 - 责任链模式

    责任链模式可以用来消除请求的发送者和接收者之间的耦合.这是通过实现一个由隐式地对请求进行处理的对象组成的链而做到的.链中的每个对象可以处理请求,也可以将其传给下一个对象. 责任链的结构: 责任链由多个 ...

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

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

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

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

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

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

  6. CSharp设计模式读书笔记(13):代理模式(学习难度:★★★☆☆,使用频率:★★★★☆)

    代理模式:给某一个对象提供一个代理或占位符,并由代理对象来控制对原对象的访问. 模式角色与结构: 示例代码: using System; using System.Collections.Generi ...

  7. 读书笔记之 - javascript 设计模式 - 接口、封装和链式调用

    javascript 采用设计模式主要有下面的三方面原因: 可维护性:设计模式有助于降低模块之间的耦合程度.这使代码进行重构和换用不同的模块变得容易,也使程序员在大型项目中合作变得容易. 沟通:设计模 ...

  8. JavaScript设计模式之代理模式

    一.代理模式概念 代理,顾名思义就是帮助别人做事,GoF对代理模式的定义如下: 代理模式(Proxy),为其他对象提供一种代理以控制对这个对象的访问.代理模式使得代理对象控制具体对象的引用.代理几乎可 ...

  9. 再起航,我的学习笔记之JavaScript设计模式08(建造者模式)

    我的学习笔记是根据我的学习情况来定期更新的,预计2-3天更新一章,主要是给大家分享一下,我所学到的知识,如果有什么错误请在评论中指点出来,我一定虚心接受,那么废话不多说开始我们今天的学习分享吧! 前几 ...

  10. 再起航,我的学习笔记之JavaScript设计模式09(原型模式)

    我的学习笔记是根据我的学习情况来定期更新的,预计2-3天更新一章,主要是给大家分享一下,我所学到的知识,如果有什么错误请在评论中指点出来,我一定虚心接受,那么废话不多说开始我们今天的学习分享吧! 我们 ...

随机推荐

  1. 【Beta阶段】发布说明

    在经历Beta阶段紧张的开发后,本次Beta阶段取得的成果虽然不如Alpha阶段多,但是也算是做到了稳中求进,一共预想了三个feature,最终做出了预想的两个feature. 新功能说明 新的主页: ...

  2. ubuntu 12.04 server + OPENACS(TR069)安装配置日记

    1. 有两个叫openacs的, openacs.org下的不是 2. 严格匹配版本:luo@bogon:~$ ls jboss-4.2.3.GA-jdk6.zip  jdk-6u41-linux-i ...

  3. MyEclipse------遍历某个路径下的(所有或特定)文件和目录

    usebean包(自己定义的,在src文件夹下面)里的java文件 FileAccept.java package usebean; import java.io.File; import java. ...

  4. --hdu 1114 Piggy-Bank(完全背包)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1114 AC code: #include<bits/stdc++.h> using nam ...

  5. iOS快速单例宏

    // 单例 #define DECLARE_SHARED_INSTANCE(className) \ + (className *)sharedInstance; #define IMPLEMENT_ ...

  6. asp.net修改web.config文件

    private void UpdateConfigFile() { var cfg = System.Web.Configuration.WebConfigurationManager.OpenWeb ...

  7. DataGridView设置不自动显示数据库中未绑定的列

    项目中将从数据库查出来的数据绑定到DataGridView,但是不想显示所有的字段.此功能可以通过sql语句控制查出来的字段数目,但是DataGridView有属性可以控制不显示未绑定的数据,从UI层 ...

  8. linux 下安装开发组件包

    最初安装redhat 时, 系统自己装的,只安装了base 包,在开发过程中,需要不停的安装某个需求包,   图省事,安装光盘下的开发组件包: 在安装光盘下,,,用命令: yum grouplist ...

  9. linux下ping的C语言实现(转)

    #include <stdio.h> #include <signal.h> #include <arpa/inet.h> #include <sys/typ ...

  10. iOS 时间处理(转)

    NSDate NSDate对象用来表示一个具体的时间点. NSDate是一个类簇,我们所使用的NSDate对象,都是NSDate的私有子类的实体. NSDate存储的是GMT时间,使用的时候会根据 当 ...