设计模式系列笔记-代理模式

写在前面:本系列文章内容为《JavaScript设计模式与开发实践》一书学习笔记,感谢作者曾探

代理模式

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

代理模式的关键是,当客户不方便直接访问一个对象或者不满足需要的时候,提供一个替身对象来控制对这个对象的访问,客户实际上访问的是替身对象。替身对象对请求做出一些处理之后,再把请求转交给本体对象

例如 A 访问 B
非代理模式:A —> B
用代理模式:A —> C —> B

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 的心情变化,所以让比较了解 A 的 B 帮忙送花

// 心情好的时候再送花
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);

2.保护代理和虚拟代理

保护代理:代理 B 可以帮助 A 过滤掉一些请求,一些请求就可以直接在代理 B处被拒绝掉。这种代理叫作保护代理
虚拟代理:虚拟代理把一些开销很大的对象,延迟到真正需要它的时候才去创建,以送花举例,使用虚拟代理可以将买花这一操作放到需要的时候再进行,防止过早的买花,等 A 心情好的时候已经不新鲜了

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

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

3.虚拟代理实现图片预加载

引入代理对象 proxyImage,在图片被真正加载好之前,页面中出现 loading 的 gif 动画, 来提示用户图片正在加载

var myImage = (function() {
  var imgNode = document.createElement('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('/loading.gif');
      img.src = src;
    }
  }
})();
proxyImage.setSrc('http://xxxx.jpg');

上面这个程序,我们并没有改变或者增加 MyImage 的接口,但是通过代理对象,实际上给系统添加了新的行为,符合开放—封闭原则的
给 img 节点设置 src 和图片预加载这两个功能,被隔离在两个对象里,它们可以各自变化而不影响对方

4.代理和本体接口的一致性

在客户看来,代理对象和本体是一致的, 代理接手请求的过程对于用户来说是透明的,用户并不清楚代理和本体的区别
作用:

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

如果代理对象和本体对象都为一个函数(函数也是对象),函数必然都能被执行,则可以认为它们也具有一致的“接口”
例如:

var myImage = (function() {
  var imgNode = document.createElement('img');
  document.body.appendChild(imgNode);
  return function(src) {
    imgNode.src = src;
  }
})();
var proxyImage = (function() {
  var img = new Image;
  img.onload = function() {
    myImage(this.src);
  }
  return function(src) {
    myImage('/loading.gif');
    img.src = src;
  }
})();
proxyImage('http://xxx.jpg');

5.代理模式其他应用

1. 虚拟代理合并HTTP请求

可以通过一个代理函数来收集一段时间之内的请求, 最后一次性发送给服务器

2. 缓存代理

编写一个简单的求乘积的程序

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 函数可以继续专注于自身的职责——计算乘积,缓存的功能是由代理对象实现的

3.用高阶函数动态创建代理

通过传入高阶函数这种更加灵活的方式,可以为各种计算方法创建缓存代理

/**************** 计算乘积 *****************/
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 

这些计算方法被当作参数传入一个专门用于创建缓存代理的工厂中, 这样一来,我们就可以为乘法、加法、减法等创建缓存代理

你可能感兴趣的:(设计模式系列笔记-代理模式)