代理模式:原理、实现与应用
1. 封装 Web 服务的通用模式
在处理 Web 服务时,我们可以抽象出一个通用模式来封装服务。此模式虽会因 Web 服务类型不同,实现细节有所差异,但能为创建自定义代理提供通用框架。
由于同源限制,Web 服务代理必须与页面来自同一域名。以下是
WebserviceProxy
类的实现:
/* WebserviceProxy class */
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);
}
};
使用该通用模式时,只需创建自定义类并继承
WebserviceProxy
,再使用
_fetchData
方法实现所需方法。以下是
StatsProxy
类作为
WebserviceProxy
子类的示例:
/* StatsProxy class. */
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
});
};
2. 目录查找示例
假设要在公司网站主页添加可搜索的人员目录,该目录应模拟实体目录页面,按姓氏首字母展示员工信息。由于此页面流量大,需尽可能节省带宽,避免小功能拖慢整个页面。
鉴于页面大小的重要性,决定仅为有需求的用户加载人员数据,这是使用虚拟代理的绝佳场景,它能延迟加载高带宽资源,直到用户需要时再加载。同时,还需告知用户目录正在加载,避免用户误以为网站故障。
首先,创建作为代理真实主题的类,该类负责获取人员数据并生成 HTML 展示数据:
/* Directory interface. */
var Directory = new Interface('Directory', ['showPage']);
/* PersonnelDirectory class, the Real Subject */
var PersonnelDirectory = function(parent) { // implements Directory
this.xhrHandler = XhrManager.createXhrHandler();
this.parent = parent;
this.data = null;
this.currentPage = null;
var that = this;
var callback = {
success: that._configure,
failure: function() {
throw new Error('PersonnelDirectory: failure in data retrieval.');
}
}
xhrHandler.request('GET', 'directoryData.php', callback);
};
PersonnelDirectory.prototype = {
_configure: function(responseText) {
this.data = eval('(' + reponseText + ')');
...
this.currentPage = 'a';
},
showPage: function(page) {
$('page-' + this.currentPage).style.display = 'none';
$('page-' + page).style.display = 'block';
this.currentPage = page;
}
};
PersonnelDirectory
类的构造函数会发起 XHR 请求获取人员数据,数据返回后调用
_configure
方法创建 HTML 并填充数据。但该类实例化时会立即获取大量数据,若在页面加载时实例化,所有用户都需加载这些数据。因此,需要使用代理延迟实例化。
创建虚拟代理的步骤如下:
1.
创建类的大纲
:
/* DirectoryProxy class, just the outline. */
var DirectoryProxy = function(parent) { // implements Directory
};
DirectoryProxy.prototype = {
showPage: function(page) {
}
};
- 实现无用代理 :
/* DirectoryProxy class, as a useless proxy. */
var DirectoryProxy = function(parent) { // implements Directory
this.directory = new PersonnelDirectory(parent);
};
DirectoryProxy.prototype = {
showPage: function(page) {
return this.directory.showPage(page);
}
};
- 实现虚拟代理 :
/* DirectoryProxy class, as a virtual proxy. */
var DirectoryProxy = function(parent) { // implements Directory
this.parent = parent;
this.directory = null;
var that = this;
addEvent(parent, 'mouseover', that._initialize); // Initialization trigger.
};
DirectoryProxy.prototype = {
_initialize: function() {
this.directory = new PersonnelDirectory(this.parent);
},
showPage: function(page) {
return this.directory.showPage(page);
}
};
- 添加加载消息并阻塞方法调用 :
/* DirectoryProxy class, with loading message. */
var DirectoryProxy = function(parent) { // implements Directory
this.parent = parent;
this.directory = null;
this.warning = null;
this.interval = null;
this.initialized = false;
var that = this;
addEvent(parent, 'mouseover', that._initialize); // Initialization trigger.
};
DirectoryProxy.prototype = {
_initialize: function() {
this.warning = document.createElement('div');
this.parent.appendChild(this.warning);
this.warning.innerHTML = 'The company directory is loading...';
this.directory = new PersonnelDirectory(this.parent);
var that = this;
this.interval = setInterval(that._checkInitialization, 100);
},
_checkInitialization: function() {
if(this.directory.currentPage != null) {
clearInterval(this.interval);
this.initialized = true;
this.parent.removeChild(this.warning);
}
},
showPage: function(page) {
if(!this.initialized) {
return;
}
return this.directory.showPage(page);
}
};
以下是创建虚拟代理的流程图:
graph TD;
A[创建类大纲] --> B[实现无用代理];
B --> C[实现虚拟代理];
C --> D[添加加载消息并阻塞方法调用];
3. 创建虚拟代理的通用模式
JavaScript 灵活性高,可创建动态虚拟代理。该代理会检查给定类的接口,创建对应方法,并在满足特定条件时延迟类的实例化。
首先创建抽象类
DynamicProxy
的不完整版本:
/* DynamicProxy abstract class, incomplete. */
var DynamicProxy = function() {
this.args = arguments;
this.initialized = false;
};
DynamicProxy.prototype = {
_initialize: function() {
this.subject = {}; // Instantiate the class.
this.class.apply(this.subject, this.args);
this.subject.__proto__ = this.class.prototype;
var that = this;
this.interval = setInterval(function() { that._checkInitialization(); }, 100);
},
_checkInitialization: function() {
if(this._isInitialized()) {
clearInterval(this.interval);
this.initialized = true;
}
},
_isInitialized: function() { // Must be implemented in the subclass.
throw new Error('Unsupported operation on an abstract class.');
}
};
然后在构造函数中添加代码,为真实主题的每个方法动态创建方法:
/* DynamicProxy abstract class, complete. */
var DynamicProxy = function() {
this.args = arguments;
this.initialized = false;
if(typeof this.class != 'function') {
throw new Error('DynamicProxy: the class attribute must be set before ' +
'calling the super-class constructor.');
}
// Create the methods needed to implement the same interface.
for(var key in this.class.prototype) {
// Ensure that the property is a function.
if(typeof this.class.prototype[key] !== 'function') {
continue;
}
// Add the method.
var that = this;
(function(methodName) {
that[methodName] = function() {
if(!that.initialized) {
return
}
return that.subject[methodName].apply(that.subject, arguments);
};
})(key);
}
};
DynamicProxy.prototype = {
_initialize: function() {
this.subject = {}; // Instantiate the class.
this.class.apply(this.subject, this.args);
this.subject.__proto__ = this.class.prototype;
var that = this;
this.interval = setInterval(function() { that._checkInitialization(); }, 100);
},
_checkInitialization: function() {
if(this._isInitialized()) {
clearInterval(this.interval);
this.initialized = true;
}
},
_isInitialized: function() { // Must be implemented in the subclass.
throw new Error('Unsupported operation on an abstract class.');
}
};
使用该类时,需先创建子类。以下是
TestProxy
类作为
DynamicProxy
子类的示例:
/* TestProxy class. */
var TestProxy = function() {
this.class = TestClass;
var that = this;
addEvent($('test-link'), 'click', function() { that._initialize(); });
// Initialization trigger.
TestProxy.superclass.constructor.apply(this, arguments);
};
extend(TestProxy, DynamicProxy);
TestProxy.prototype._isInitialized = function() {
... // Initialization condition goes here.
};
子类需满足以下四个要求:
1.
this.class
必须设置为真实主题的类。
2. 必须创建某种初始化触发器。
3. 必须调用超类的构造函数。
4. 必须实现
_isInitialized
方法,根据真实主题是否初始化返回
true
或
false
。
4. 代理模式的优缺点
| 类型 | 优点 | 缺点 |
|---|---|---|
| 远程代理 |
- 可将远程资源视为本地 JavaScript 对象,减少访问远程资源的胶水代码,提供统一接口。
- 若远程资源 API 更改,只需在一处修改代码。 - 将与资源相关的数据存储在一处。 |
- 掩盖复杂行为,访问速度比本地资源慢很多。
- 需要使用回调函数,增加代码复杂度。 - 与远程资源通信可能存在可靠性问题。 |
| 虚拟代理 |
- 提供效率,延迟高成本资源的加载。
- 可在真实主题加载时提供加载消息或虚拟 UI。 - 可避免不必要的资源加载。 |
- 掩盖延迟实例化的逻辑,程序员不清楚对象创建的触发条件。
- 增加项目复杂度和代码量。 |
代理模式有多种形式,包括保护代理、远程代理和虚拟代理。保护代理在 JavaScript 中无法实现,远程代理可封装其他语言编写的 Web 服务,虚拟代理可控制高成本类或对象的访问。使用代理模式时,需确保其能使代码更简洁、模块化或高效,否则直接访问真实主题会更简单。
代理模式:原理、实现与应用
5. 代理模式的类型及工作原理
代理模式主要有以下几种类型,下面详细介绍它们的工作原理:
-
保护代理
:用于控制哪些客户端可以访问真实主题的方法。不过在 JavaScript 中,由于语言特性的限制,无法实现保护代理,所以在实际应用中较少涉及。
-
远程代理
:它的核心作用是控制对远程资源的访问。在像 Java 这样的语言中,远程代理可以直接连接到持久的 Java 虚拟机,并传递方法调用。但在客户端 JavaScript 里,虽然无法实现这种方式,但远程代理在封装其他语言编写的 Web 服务时非常有用。它能让开发者将远程资源当作本地对象来处理,极大地简化了对远程资源的访问。例如,当需要访问多个不同的 Web 服务时,可以创建一个抽象的通用远程代理类,然后为每个需要访问的 Web 服务创建子类。
-
虚拟代理
:主要用于控制对创建或维护成本较高的类或对象的访问。在 JavaScript 中,由于终端用户的浏览器内存有限,虚拟代理能很好地解决资源加载的问题。当真实主题加载缓慢时,虚拟代理可以提供加载消息或虚拟用户界面(UI),让用户在真实 UI 加载完成前进行交互。
6. 代理模式的实际应用场景
代理模式在不同的场景下有广泛的应用,以下是一些常见的例子:
-
远程代理的应用
:在开发跨域的 Web 应用时,需要访问其他服务器上的资源。此时可以使用远程代理来封装对这些远程资源的访问,将复杂的跨域请求和数据处理逻辑隐藏在代理中,让开发者可以像操作本地对象一样操作远程资源。例如,一个电商网站需要从不同的供应商服务器获取商品信息,就可以使用远程代理来统一管理这些请求。
-
虚拟代理的应用
:在网页加载大量图片或数据时,使用虚拟代理可以延迟这些资源的加载,直到用户需要查看时再进行加载。比如,一个图片库网站,用户在浏览图片列表时,只加载图片的缩略图,当用户点击某张图片时,再使用虚拟代理加载高清大图,这样可以提高页面的加载速度,减少不必要的带宽消耗。
7. 代理模式的使用建议
在使用代理模式时,需要根据具体的需求和场景来选择合适的代理类型,并注意以下几点:
-
明确需求
:在决定使用代理模式之前,要仔细评估是否真的需要代理提供的功能。如果直接访问真实主题就能满足需求,那么使用代理只会增加项目的复杂度和代码量。
-
合理选择代理类型
:根据资源的性质和访问特点,选择合适的代理类型。例如,如果是访问远程资源,就选择远程代理;如果是处理高成本资源的加载,就选择虚拟代理。
-
做好文档记录
:由于代理模式会掩盖一些复杂的行为,为了让其他开发者能够理解和使用代理,需要做好详细的文档记录,说明代理的功能、使用方法和注意事项。
8. 总结
代理模式是一种非常有用的设计模式,它通过不同的代理类型控制对资源的访问。虽然代理模式有很多优点,如简化远程资源访问、提高资源加载效率等,但也存在一些缺点,如增加代码复杂度、掩盖复杂行为等。在实际应用中,要根据具体情况合理使用代理模式,确保它能使代码更加简洁、模块化和高效。通过合理运用代理模式,可以更轻松地访问原本难以处理的资源,提升项目的性能和用户体验。
以下是代理模式类型及特点的总结表格:
| 代理类型 | 能否在 JavaScript 实现 | 作用 | 优点 | 缺点 |
| ---- | ---- | ---- | ---- | ---- |
| 保护代理 | 否 | 控制客户端对真实主题方法的访问 | 无 | 无 |
| 远程代理 | 是 | 封装远程 Web 服务,将远程资源视为本地对象 | - 减少胶水代码,提供统一接口
- API 更改时只需一处修改代码
- 集中存储资源相关数据 | - 访问速度慢
- 使用回调增加复杂度
- 通信有可靠性问题 |
| 虚拟代理 | 是 | 控制高成本类或对象的访问 | - 延迟高成本资源加载
- 提供加载消息或虚拟 UI
- 避免不必要的资源加载 | - 掩盖延迟实例化逻辑
- 增加项目复杂度和代码量 |
以下是代理模式使用流程的 mermaid 流程图:
graph LR;
A[明确需求] --> B{是否需要代理};
B -- 是 --> C{选择代理类型};
C -- 远程代理 --> D[创建远程代理类];
C -- 虚拟代理 --> E[创建虚拟代理类];
B -- 否 --> F[直接访问真实主题];
D --> G[使用远程代理访问资源];
E --> H[使用虚拟代理控制资源加载];
通过以上内容,我们对代理模式有了更深入的了解,希望能帮助你在实际开发中更好地运用代理模式。
代理模式详解与实践
超级会员免费看
3711

被折叠的 条评论
为什么被折叠?



