「这是我参与11月更文挑战的第16天,活动详情查看:2021最后一次更文挑战」
代理模式是一种结构型设计模式,让你能够提供对象的替代品或其占位符。代理控制着对于原对象的访问,并允许在将请求提交给对象前后进行一些处理。
描述
比如现实生活中的手机支付和信用卡是银行账户的代理,银行账户则是一大捆现金的代理。它们都实现了同样的接口,均可用于进行支付。它使消费者和商家都很满意,不必随身携带大量现金和收到假币找零钱的问题。
作用
在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。
适合场景
- 延迟初始化 (虚拟代理)。如果你有一个偶尔使用的重量级服务对象,一直保持该对象运行会消耗系统资源时, 可使用代理模式。
- 访问控制(保护代理)。如果你只希望特定客户端使用服务对象,这里的对象可以是操作系统中非常重要的部分,而客户端则是各种已启动的程序(包括恶意程序),此时可使用代理模式。
- 本地执行远程服务(远程代理)。适用于服务对象位于远程服务器上的情形。
- 缓存请求结果 (缓存代理)。
实现思路
- 如果没有现成的服务接口, 你就需要创建一个接口来实现代理和服务对象的可交换性。 从服务类中抽取接口并非总是可行的, 因为你需要对服务的所有客户端进行修改, 让它们使用接口。 备选计划是将代理作为服务类的子类, 这样代理就能继承服务的所有接口了。
- 创建代理类, 其中必须包含一个存储指向服务的引用的成员变量。 通常情况下, 代理负责创建服务并对其整个生命周期进行管理。 在一些特殊情况下, 客户端会通过构造函数将服务传递给代理。
- 根据需求实现代理方法。 在大部分情况下, 代理在完成一些任务后应将工作委派给服务对象。
- 可以考虑新建一个构建方法来判断客户端可获取的是代理还是实际服务。 你可以在代理类中创建一个简单的静态方法, 也可以创建一个完整的工厂方法。
- 可以考虑为服务对象实现延迟初始化。
保护代理
现在刘备为了扩展业务,打算去邀请诸葛亮出生,所以我们可以用代码这么描述,定义一个刘备和诸葛亮的对象,刘备有一个邀请的方法,诸葛亮有一个接受礼物的方法,正常我们可以这样:
// 刘备
class Bei {
constructor(user) {
this.user = user;
}
invite(){
this.user.reception('草鞋')
}
}
// 亮
class Liang {
// 收到礼物
reception(gift){
console.log('亮收到礼物:' + gift); // 亮收到草鞋,很开心
}
reject(gift) {
console.log('拒绝礼物:' + gift)
}
}
const bei = new Bei(new Liang());
bei.invite()
// 亮收到礼物:草鞋
但是实际上诸葛亮比较大牌,不是谁都可以见的,所以需要一个中间人门童,来过滤掉不喜欢的人。这个门童就是代理
// 刘备
class Bei {
constructor(user) {
this.user = user;
}
invite(){
this.user.reception('草鞋')
}
}
class Liang {
// 收到礼物
reception(gift){
console.log('亮收到礼物:' + gift)
}
reject(gift) {
console.log('拒绝礼物:' + gift)
}
}
class Mentong{
constructor(liang) {
this.liang = liang;
}
// 代替接收礼物
reception(gift){
console.log('门童收到礼物:' + gift)
// 按照诸葛亮的要求,只收草鞋
if(gift === '草鞋') {
// 给诸葛亮
this.liang.reception(gift)
} else {
this.liang.reject(gift)
}
}
}
const mentong = new Mentong(new Liang()); // 创建一个代理诸葛亮收礼物的门童
const bei = new Bei(mentong); // 刘备把礼物先给门童
bei.invite()
// 门童收到礼物:草鞋
// 亮收到礼物:草鞋
这个就是保护代理,保护诸葛亮只能收到草鞋,其他人来送金银珠宝的话统统不要;
虚拟代理
如果诸葛亮要求变了,要送其他礼物才能见到他,刘备很着急,他没有其他礼物,所以直接给钱给门童,让门童买礼物给诸葛亮
class Mentong{
constructor(liang) {
this.liang = liang;
}
// 代替接收礼物
reception(gift){
const book = new Book(); // 买书
this.liang.reception(book)
}
}
通过门童刘备送了本书,这个就是虚拟代理
代理实现图片预加载
通常如果我么需要创建一个img标签,然后展示一张图片。可以这么实现
// 创建一个本体对象
var myImage = (function(){
// 创建标签
var imgNode = document.createElement('img');
// 添加到页面
document.body.appendChild(imgNode);
return {
// 设置图片的src
setSrc: function(src){
// 更改src
imgNode.src = src;
}
}
})();
myImage.setSrc('图片地址');
但是由于网络或者其他原因,导致图片加载完成前会出现空白,所以我么可以实现一个预加载代理
// 创建一个本体对象
var myImage = (function(){
// 创建标签
var imgNode = document.createElement('img');
// 添加到页面
document.body.appendChild(imgNode);
return {
// 设置图片的src
setSrc: function(src){
// 更改src
imgNode.src = src;
}
}
})();
// 创建代理对象
var proxyImage = (function(){
// 创建一个新的img标签
var img = new Image();
// img 加载完成事件
img.onload = function(){
// 调用 myImage 替换src方法
myImage.setSrc(this.src);
}
return {
// 代理设置地址
setSrc: function(src){
// 预加载 loading
myImage.setSrc('loading.gif');
// 赋值正常图片地址
img.src = src;
}
}
})();
proxyImage.setSrc('图片地址');
图片加载完成之前通过代理先展示loading