当前位置: 首页 > news >正文

第六章、代理模式

第六章、代理模式

代理模式是为一个对象提供一个代用品或占位符,以便控制对它的访问。

代理模式的关键是,当客户不方便直接访问一个对象或者不满足需求时,提供一个替身对象来控制对这个对象的访问,客户实际上访问的是替身对象。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

6.1举个栗子

小明想要对女神A表白,送鲜花,可是他害羞不敢自己送,这时找来B,让B替小明送给A。这个模式就是代理模式。

var Flower = function(){}; var xiaoming = { sendFlower: function( target){ var flower = new Flower(); target.receiveFlower( flower ); } 
}; var B = { receiveFlower: function( flower ){ A.receiveFlower( flower );} 
}; var A = { receiveFlower: function( flower ){ console.log( '收到花 ' + flower ); } 
}; xiaoming.sendFlower( B );

这样就是简单的代理结构,但似乎这样做代理并没有什么作用,反而显得代码冗余。但是有了代理的思想就是一个好的起点。

再看这个场景:

小明表白的成功概率和女神A的心情有关,A心情好的时候成功概率是60%,心情不好的表白成功时候几乎为0。而小明不知道A是什么心情,B知道A的心情,就可以通过把鲜花交给B,由B判断A的心情好坏再把鲜花转交给女神A,代码如下:

var Flower = function(){}; var xiaoming = { sendFlower: function( target){ var flower = new Flower(); target.receiveFlower( flower ); } 
}; var B = { receiveFlower: function( flower ){ A.listenGoodMood(function(){    // 监听A的好心情 A.receiveFlower( flower ); }); } 
}; var A = { receiveFlower: function( flower ){ console.log( '收到花 ' + flower ); }, listenGoodMood: function( fn ){ setTimeout(function(){    // 假设10秒之后A的心情变好fn();         }, 10000 ); } 
}; xiaoming.sendFlower( B ); 

6.2保护代理和虚拟代理

比如送花的人年龄太大或者没有本次车,这种请求就可以直接在代理B处过滤掉,这种代理叫做保护代理

另外,假设现实中的花价格不菲,导致在程序世界里,new Flower也是一个代价昂贵的操作, 那么我们可以把new Flower的操作交给代理B去执行,代理B会选择在A心情好时再执行new Flower,这是代理模式的另一种形式,叫作虚拟代理。虚拟代理把一些开销很大的对象,延迟到 真正需要它的时候才去创建。代码如下:

var B = { receiveFlower: function( flower ){ A.listenGoodMood(function(){    // 监听A的好心情 var flower = new Flower();    // 延迟创建flower 对象 A.receiveFlower( flower ); }); } 
}; 

保护代理用于控制不同权限的对象对目标对象的访问,但在JavaScript并不容易实现保护代 理,因为我们无法判断谁访问了某个对象。而虚拟代理是最常用的一种代理模式,本章主要讨论 的也是虚拟代理。

6.3虚拟代理实现图片预加载

在Web开发中,图片预加载是一种常用的技术,如果直接给某个img标签节点设置src属性, 由于图片过大或者网络不佳,图片的位置往往有段时间会是一片空白。常见的做法是先用一张 loading图片占位,然后用异步的方式加载图片,等图片加载好了再把它填充到img节点里,这种 场景就很适合使用虚拟代理。

让我们实现一个虚拟代理,首先创建一个普通对象,负责在页面中创建一个img标签,并提供一个setSrc接口对外暴露。

src属性:

var myImage = (function(){ var imgNode = document.createElement( 'img' ); document.body.appendChild( imgNode ); return { setSrc: function( src ){ imgNode.src = src;         }  } 
})();  myImage.setSrc( 'http:// imgcache.qq.com/music/photo/k/000GGDys0yA0Nk.jpg' );

我们把网速调制5KB/s,然后通过MyImage.setSrc,这是可以看到在图片加载好之前,页面中有一段空白时间。

这时候引入代理对象proxyImage,通过这个代理对象,在图片被真正加载好之前,页面先出现一张占位的longing图片来提示用户图片正在加载,代码如下:

var myImage = (function() {var imgNode = document.createEleement('img');document.body.appendChild(imgNode);return {setSrc: function(src) {imgNode.src = src;}}
})();
var proxyImage = (function() {var img = new Image;img.onload = function() {myImage.setSrc(this.src);}return {setSrc: function(src) {myImage.setSrc( 'file:// /C:/Users/svenzeng/Desktop/loading.gif' ); img.src = src;         }}
})
proxyImage.setSrc('http:// imgcache.qq.com/music/photo/k/000GGDys0yA0Nk.jpg');

这时候我们可以通过proxyImage间接地访问MyImage。proxyImage控制了对客户MyImage的访问,并在次过程加入了额外的操作,比如在图片加载出之前,先把img节点的src设置为一张本地的loading图片。

6.4代理的意义

先实现一个不用代理的预加载图片函数:

var MyImage = (function() {var imgNode = document.createElement('img');document.body.appendChild(imgNode);var img = new Image;img.onload = function() {imgNode.src = img.src;};return {setSrc: function(src) {imgNode.src = 'file:// /C:/Users/svenzeng/Desktop/loading.gif'img.src = src;}}
})();
MyImage.setSrc( 'http:// imgcache.qq.com/music/photo/k/000GGDys0yA0Nk.jpg' ); 

为了说明代理的意义,下面我们引入一个面向对象设计的原则——单一职责原则。

单一职责原则指的是,就一个类(通常也包括对象和函数等)而言,应该仅有一个引起它变化的原因。如果一个对象承担了多个职责,耦合程度就加大了,当发生变化时,可能会出现意外的破坏。面向对象设计鼓励将行为分布到细粒度的对象当中。也就是单一职责。

职责被定义为**“引起变化的原因”**。上段代码中的MyImage对象除了负责给img节点设置src 外,还要负责预加载图片。我们在处理其中一个职责时,有可能因为其强耦合性影响另外一个职 责的实现。

另外,在面向对象的程序设计中,大多数情况下,若违反其他任何原则,同时将违反开放— 封闭原则。如果我们只是从网络上获取一些体积很小的图片,或者5年后的网速快到根本不再需 要预加载,我们可能希望把预加载图片的这段代码从MyImage对象里删掉。这时候就不得不改动 MyImage对象了。 实际上,我们需要的只是给img节点设置src,预加载图片只是一个锦上添花的功能。如果 能把这个操作放在另一个对象里面,自然是一个非常好的方法。于是代理的作用在这里就体现出 来了,代理负责预加载图片,预加载的操作完成之后,把请求重新交给本体MyImage。

纵观整个程序,我们并没有改变或者增加MyImage的接口,但是通过代理对象,实际上给系 统添加了新的行为。这是符合开放—封闭原则的。给img节点设置src和图片预加载这两个功能, 被隔离在两个对象里,它们可以各自变化而不影响对方。何况就算有一天我们不再需要预加载, 那么只需要改成请求本体而不是请求代理对象即可。

6.5代理和本体接口的一致性

上一节说到,如果有一天我们不再需要预加载,那么就不再需要代理对象,可以选择直接请 求本体。其中关键是代理对象和本体都对外提供了 setSrc 方法,在客户看来,代理对象和本体 是一致的, 代理接手请求的过程对于用户来说是透明的,用户并不清楚代理和本体的区别,这 样做有两个好处。

  • 用户可以放心地请求代理,他只关心是否能得到想要的结果。
  • 在任何使用本体的地方都可以替换成使用代理。

6.6虚拟代理合并HTTP请求

再举个栗子:有多个复选框,每点击一个就会发送一次请求,如果一秒钟点多个,那么请求会带来很大的开销。

解决方案:可以通过一个代理来收集一段时间内的请求,最后一次性发送给服务器。比如我们等待2秒之后才把这个2秒之内需要同步文件的ID打包发送给服务器。

6.7虚拟代理在惰性加载中的应用

假设有一个迷你控制台的项目——miniConsole.js,它有一个log函数,专门用于打印参数。

复制代码// miniConsole.js代码let miniConsole = {log: function(){// 真正代码略console.log( Array.prototype.join.call( arguments ) );}
};export default miniConsole

因为这个控制台项目,是只在控制台展示的时候才需要的,我们希望他在有必要的时候才开始加载它,比如按F2的时候,加载miniConsole.js,就可以使用代理模式,惰性加载miniConsole.js。

大致的步骤是:

  1. 在用户敲击F2的时候,才去动态引入miniConsole.js的script标签
  2. 在用户敲击F2之前执行过的log命令,都会被缓存到代理对象内部的cache缓存数组内
  3. 等动态引入miniConsole.js的操作完成后,再从中逐一取出并执行。

详细代码如下:

复制代码// proxyMiniConsole.js代码// miniConsole的代理对象
let proxyMiniConsole = (function(){// 存储每次执行log时的回调函数let cache = [];let handler = function( ev ){// 如果用户按了F2唤出了控制台if ( ev.keyCode === 113 ){// 执行引入miniConsole.js的操作let script = document.createElement( 'script' );script.src = 'miniConsole.js';document.getElementsByTagName( 'head' )[0].appendChild( script );document.body.removeEventListener( 'keydown', handler );// 只加载一次miniConsole.jsscript.onload = function(){// 如果miniConsole.js的script标签引入并加载完成for ( var i = 0, fn; fn = cache[ i++ ]; ){// 遍历所有缓存的回调函数并执行fn();}};}};// 监听键盘按键敲击事件document.body.addEventListener( 'keydown', handler, false );return {// 返回代理后的方法log: function(){// 获取传入的所有参数let args = arguments;// 向缓存列表加入要打印的参数cache.push( function(){return miniConsole.log.apply( miniConsole, args );});}}
})();miniConsole.log( 11 );      // 开始打印log

6.8缓存代理

缓存代理可以为一些开销大的运算结果提供暂时的存储,在下次运算时,如果传递进来的参 数跟之前一致,则可以直接返回前面存储的运算结果。

举个栗子:

1.计算乘积

先创建一个用于求乘积的函数:  
var mult = function(){ console.log( '开始计算乘积' ); var a = 1; for ( var i = 0, l = arguments.length; i < l; i++ ){ a = a * arguments[i];  }  return a; 
}; mult( 2, 3 );    // 输出:6 
mult( 2, 3, 4 );    // 输出:24 
现在加入缓存代理函数: 
var proxyMult = (function(){ var cache = {}; return function(){ var args = Array.prototype.join.call( arguments, ',' ); if ( args in cache ){ return cache[ args ]; } return cache[ args ] = mult.apply( this, arguments ); } 
})(); proxyMult( 1, 2, 3, 4 );    // 输出:24 proxyMult( 1, 2, 3, 4 );    // 输出:24 
当我们第二次调用proxyMult( 1, 2, 3, 4 )的时候,本体mult函数并没有被计算,proxyMult
直接返回了之前缓存好的计算结果。 
通过增加缓存代理的方式,mult函数可以继续专注于自身的职责——计算乘积,缓存的功能
是由代理对象实现的。

6.9用高阶函数动态创建代理

/**************** 计算乘积 *****************/ 
var mult = function(){ var a = 1; for ( var i = 0, l = arguments.length; i < l; i++ ){ a = a * arguments[i];  } return a; 
}; /**************** 计算加和 *****************/ 
var plus = function(){ var a = 0; for ( var i = 0, l = arguments.length; i < l; i++ ){ a = a + arguments[i];  } return a; 
}; /**************** 创建缓存代理的工厂 *****************/ 
var createProxyFactory = function( fn ){ var cache = {}; return function(){ var args = Array.prototype.join.call( arguments, ',' ); if ( args in cache ){ return cache[ args ]; } return  cache[ args ] = fn.apply( this, arguments ); } 
}; var proxyMult = createProxyFactory( mult ), 
proxyPlus = createProxyFactory( plus ); alert ( proxyMult( 1, 2, 3, 4 ) );    // 输出:24 
alert ( proxyMult( 1, 2, 3, 4 ) );    // 输出:24 
alert ( proxyPlus( 1, 2, 3, 4 ) );    // 输出:10 
alert ( proxyPlus( 1, 2, 3, 4 ) );    // 输出:10 

6.10其他代理模式

  • 防火墙代理:控制网络资源的访问,保护主题不让“坏人”接近。
  • 远程代理:为一个对象在不同的地址空间提供局部代表,在Java中,远程代理可以是另 一个虚拟机中的对象。
  • 保护代理:用于对象应该有不同访问权限的情况。
  • 智能引用代理:取代了简单的指针,它在访问对象时执行一些附加操作,比如计算一个 对象被引用的次数。
  • 写时复制代理:通常用于复制一个庞大对象的情况。写时复制代理延迟了复制的过程, 当对象被真正修改时,才对它进行复制操作。写时复制代理是虚拟代理的一种变体,DLL (操作系统中的动态链接库)是其典型运用场景。

相关文章:

  • TenorFlow多层感知机识别手写体
  • 基于单片机的智能交通控制系统研究
  • [C# WPF] 如何给控件添加边框(Border)?
  • C语言:螺旋阵
  • 2.8日学习打卡----初学RabbitMQ(三)
  • 【NLP】MHA、MQA、GQA机制的区别
  • 鸿蒙系统优缺点,能否作为开发者选择
  • React和Vue 中的 router 实现原理如何
  • php数据类型以及运算符、判断条件
  • CSS 评分器星星效果
  • TiDB 在医疗保障信息平台的应用实践
  • 鸿蒙(HarmonyOS)项目方舟框架(ArkUI)之AlphabetIndexer组件
  • 【优化数学模型】3. 基于Python的整数规划-指派问题求解
  • 【机器学习案例3】从科学论文图片中提取标题、作者和摘要【含源码】
  • linux---内存管理
  • 「面试题」如何实现一个圣杯布局?
  • 【108天】Java——《Head First Java》笔记(第1-4章)
  • 【跃迁之路】【699天】程序员高效学习方法论探索系列(实验阶段456-2019.1.19)...
  • android 一些 utils
  • Angularjs之国际化
  • Asm.js的简单介绍
  • codis proxy处理流程
  • css系列之关于字体的事
  • eclipse的离线汉化
  • hadoop集群管理系统搭建规划说明
  • HTTP请求重发
  • Linux后台研发超实用命令总结
  • Linux快速复制或删除大量小文件
  • mockjs让前端开发独立于后端
  • node-sass 安装卡在 node scripts/install.js 解决办法
  • vue中实现单选
  • Webpack入门之遇到的那些坑,系列示例Demo
  • Yeoman_Bower_Grunt
  • ------- 计算机网络基础
  • 看域名解析域名安全对SEO的影响
  • 力扣(LeetCode)21
  • 猫头鹰的深夜翻译:JDK9 NotNullOrElse方法
  • 你不可错过的前端面试题(一)
  • 格斗健身潮牌24KiCK获近千万Pre-A轮融资,用户留存高达9个月 ...
  • 积累各种好的链接
  • 扩展资源服务器解决oauth2 性能瓶颈
  • 曜石科技宣布获得千万级天使轮投资,全方面布局电竞产业链 ...
  • ​人工智能之父图灵诞辰纪念日,一起来看最受读者欢迎的AI技术好书
  • #!/usr/bin/python与#!/usr/bin/env python的区别
  • (1)STL算法之遍历容器
  • (C++)八皇后问题
  • (day 2)JavaScript学习笔记(基础之变量、常量和注释)
  • (差分)胡桃爱原石
  • (二)PySpark3:SparkSQL编程
  • (一)spring cloud微服务分布式云架构 - Spring Cloud简介
  • .axf 转化 .bin文件 的方法
  • .NET Core SkiaSharp 替代 System.Drawing.Common 的一些用法
  • .net framework profiles /.net framework 配置
  • .Net Remoting常用部署结构
  • .NET:自动将请求参数绑定到ASPX、ASHX和MVC(菜鸟必看)