代理模式是为一个对象提供一个代用品或占位符,以便控制对它的访问。
代理模式是一种非常有意义的模式,在生活中可以找到很多代理模式的场景,比如,明星都有经纪人作为代理,如果详情明星办一场商业演出,那么只能联系他的经纪人,经纪人会把细节谈好,再把合同交给明星。
第一个例子,小明追 MM 的故事
小明遇到了一个百分百女生,两天后小明决定送一朵花表白,小明知道女生有一个闺蜜,于是拜托她送花给女生。
用代理模拟一下过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| var Flower = function() {};
var xiaoming = { senFlower: function(target) { var flower = new Flower(); target.receiveFlower(flower); } }; var A = { receiveFlower: function(flower) { console.log("收到花" + flower); } };
xiaoming.senFlower(A);
var Flower = function() {};
var xiaoming = { senFlower: 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.senFlower(B);
|
至此完成一个最简单的代理模式编写。
此处的代理模式其实毫无用处,他做的知识把请求简单的交给本体。
虚拟代理实现图片预加载
在 web 开发中,图片预加载是一种常用的技术,比如直接给 img 标签节点设置 src 属性,现用一张 loading 图片占位,然后异步加载图片,等到图片加载好了再把它填充到 img 节点。
下面实现这个虚拟代理,首先创建一个普通的本体对象,这个对象负责往页面中创建一个 img 标签,并且提供一个对外的 setSrc 接口,外接调用这个接口,便可以给该 img 标签设置 src 属性。
1 2 3 4 5 6 7 8 9 10
| var myImage = (function() { var imgNode = docoment.createElement("img"); document.body.appendChild(imgNode); return { setSrc: function(src) { imgNode.src = src; } }; })(); myImage.setSrc("图片地址");
|
现在开始引用代理对象 proxyImage,通过这个代理对象,在图片被真正加载好之前,页面将出现一张占位的 loading.gif,来提示用户图片正在加载。
1 2 3 4 5 6 7 8 9 10 11 12 13
| var proxyImage = (function() { var img = new myImage(); img.onload = function() { myImage.setSrc(this.src); }; return { setSrc: function(src) { myImage.setSrc("图片地址"); img.src = src; } }; })(); proxyImage.setSrc("图片地址");
|
代理的意义
为了说明代理的意义,引入一个面向对象的设计原则—单一职责原则。
单一职责原则指的是,就一个类而言,应该仅有一个引起它变化的原因,如果一个对象承担了多项职责,就意味着这个对象将变得巨大,引起他变化的原因可能会有多个,面向对象设计鼓励奖行为分不到细粒度的对象中,如果一个对象承担的职责过多,等于把这些职责耦合到了一起,这种耦合会导致脆弱和低内聚的设计,当变化发生时,设计可能遭到意外的破坏。
代理和本体接口的一致性
在客户看来,代理对象和本体是一直的,代理接手请求的过程对于用户来说是透明的,用户并不清楚代理和本体的区别,这样做有两个好处
- 用户可以放心的请求代理,他只关心是否能得到想要的额结果。
- 在任何使用本体的地方都可以替换成使用代理。
虚拟代理合并 HTTP 请求
在 web 开发中也许开销最大的就是网络请求,下面通过一个例子合并请求。
现在页面放置 checkbox 节点,点击一个按钮就会触发一个请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <body> <input type='checkbox' id='1' /> <input type='checkbox' id='2‘ /> <input type='checkbox' id='3' /> <input type='checkbox' id='4' /> <input type='checkbox' id='5' /> <input type='checkbox' id='6' /> <input type='checkbox' id='7' /> <input type='checkbox' id='8' /> <input type='checkbox' id='9' /> </body>
var synchronousFile = function(id){ console.log('开始同步文件id为'+id) } var checkbox = document.getElementsBytagName('input') for(var i = 0,c; c = checkbox[i++]){ c.onclick = function(){ if(this.checked === true){ synchronousFile(this.id) } } }
|
选择一个 CheckBox 就会触发一次请求,以我单身 25 年手速,一秒可以点九个,由此可见频发触发网络请求将会带来相当大的开销
解决方案是通过一个代理函数 proxySynchronousFile 来手机一段时间之内的请求(函数节流?)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| var synchronousFile = function(id) { console.log("开始同步" + id); }; var proxySynchronousFile = (function() { var cache = [], timer; return function(id) { cache.push(id); if (timer) { return; } }; timer = setTimeout(function() { synchronousFile(cache.join(",")); timer = null; cache.length = 0; }, 1000); })(); var checkbox = document.getElementsByTagName("input"); for (var i = 0, c; (c = checkbox[i]); i++) { c.onclick = function() { if (this.checked === true) { proxySynchronousFile(this.id); } }; }
|
缓存代理
缓存代理可以为一些开销大的运算结果提供暂时的储存,下次运算时,如果传递进来的参数和以前一直,则返回前面储存的运算结果。
- 乘积计算
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| 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); mult(3, 3, 5);
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); proxyMult(1, 2, 3, 4);
|