设计模式-代理模式

代理模式

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

  • 保护代理:代理 B 可以帮助 A 过滤掉一些请求,类似明星经纪人

  • 虚拟代理:虚拟代理把一些开销很大的对象,延迟到 真正需要它的时候才去创建。

引入
// // 追女孩事件
  // // 不使用代理
  // let Flower = function(){}
  // let xm = {
  //   sendFlower:function(target) {
  //     let flower = new Flower()
  //     target.receiverFlower(flower)
  //   }
  // }
  // let a = {
  //   receiverFlower: function(flower) {
  //     console.log('收到花' , flower)
  //   }
  // }
  // xm.sendFlower(a)

  // // 引入代理
  // let Flower = function(){}
  // let xm = {
  //   sendFlower:function(target) {
  //     let flower = new Flower()
  //     target.receiverFlower(flower)
  //   }
  // }
  // let b = {
  //   receiverFlower: function(flower) {
  //     a.receiverFlower(flower)
  //   }
  // }
  // let a = {
  //   receiverFlower: function(flower) {
  //     console.log('收到花' , flower)
  //   }
  // }
  // xm.sendFlower(b)

  // 观察心情
   let Flower = function(){}
  let xm = {
    sendFlower:function(target) {
      // let flower = new Flower()
      // 不在这里给花,而是叫b等a心情好的时候再去买花给她
      target.receiverFlower()
    }
  }
  let b = {
    receiverFlower: function() {
      a.listenGoodMood(function(){
        // 虚拟代理,在需要的时候才创建。
        let flower = new Flower()
        a.receiverFlower(flower)
      })
    }
  }
  let a = {
    receiverFlower: function(flower) {
      console.log('收到花' , flower)
    },
    listenGoodMood: function(fn) {
      setTimeout(function(){
        console.log('心情变好')
        fn() // 送花
      }, 300)
    }
  }
  xm.sendFlower(b)
图片懒加载

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

  1. 普通加载,如果图片因为网络等原因不出来或者根据就没有则会显示空白
let myImage = (function(){
    let imgNode = document.createElement('img')
    document.body.appendChild(imgNode)
    return {
      setSrc: function(src) {
        imgNode.src = src
      }
    }
  })()
  myImage.setSrc('http:///lf3-cdn-tos.bytescm.com/obj/static/xitu_juejin_web/dcec27cc6ece0eb5bb217e62e6bec104.svg')
  1. 一般写法,加入懒加载
let myImage = (function(){
    let imgNode = document.createElement('img')
    document.body.appendChild(imgNode)
    let img = new Image
    img.onload = function() {
      imgNode.src = img.src
    }
    return {
      setSrc: function(src){
        imgNode.src = 'images/1.svg'
        setTimeout(function(){
          img.src = src
        }, 300)
      }
    }
  })()
  myImage.setSrc('http:///lf3-cdn-tos.bytescm.com/obj/static/xitu_juejin_web/dcec27cc6ece0eb5bb217e62e6bec104.svg')

单一职责原则指的是,就一个类(通常也包括对象和函数等)而言,应该仅有一个引起它变 化的原因。如果一个对象承担了多项职责,就意味着这个对象将变得巨大,引起它变化的原因可 能会有多个。面向对象设计鼓励将行为分布到细粒度的对象之中,如果一个对象承担的职责过多, 等于把这些职责耦合到了一起,这种耦合会导致脆弱和低内聚的设计

上段代码中的 MyImage 对象除了负责给 img 节点设置 src 外,还要负责预加载图片。我们在处理其中一个职责时,有可能因为其强耦合性影响另外一个职 责的实现。 另外,在面向对象的程序设计中,大多数情况下,若违反其他任何原则,同时将违反开放— 封闭原则。如果我们只是从网络上获取一些体积很小的图片,或者 5 年后的网速快到根本不再需 要预加载,我们可能希望把预加载图片的这段代码从 MyImage 对象里删掉。这时候就不得不改动 MyImage 对象了

  1. 代理模式
let myImage = (function(){
    let imgNode = document.createElement('img')
    document.body.appendChild(imgNode)
    return {
      setSrc: function(src) {
        imgNode.src = src
      }
    }
  })()
  let proxyImage = (function(){
    let img = new Image
    img.onload = function(){
      myImage.setSrc(this.src)
    }
    return {
      setSrc: function(src){
        myImage.setSrc('images/1.svg')
        setTimeout(function(){
          img.src = src
        }, 500)
      }
    }
  })()
  proxyImage.setSrc('http:///lf3-cdn-tos.bytescm.com/obj/static/xitu_juejin_web/dcec27cc6ece0eb5bb217e62e6bec104.svg')

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

  1. 改进

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

let myImage = (function(){
    let imgNode = document.createElement('img')
    document.body.appendChild(imgNode)
    return function(src){
      imgNode.src = src
    }
  })()
  let proxyImage = (function(){
    let img = new Image
    img.onload = function(){
      myImage(this.src)
    }
    return function(src) {
      myImage('images/1.svg')
      setTimeout(function(){
        img.src = src
      })
    }
  })()
  proxyImage('http:///lf3-cdn-tos.bytescm.com/obj/static/xitu_juejin_web/dcec27cc6ece0eb5bb217e62e6bec104.svg')
虚拟代理合并HTTP请求

汇报事件,员工汇报给组长,组长整理好汇报给总监。

同步文件事件

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Document</title>
</head>

<body>
  <input type="checkbox" id="1">1</input><input type="checkbox" id="2">2</input><input type="checkbox"
    id="3">3</input><input type="checkbox" id="4">4</input><input type="checkbox" id="5">5</input><input type="checkbox"
    id="6">6</input><input type="checkbox" id="7">7</input><input type="checkbox" id="8">8</input><input type="checkbox"
    id="9">9</input>
</body>
<script>
  let synchronousFile = function (id) {
    console.log('同步文件', id)
  }
  let checkbox = document.getElementsByTagName('input')
  for (let i = 0, c; c = checkbox[i++];) {
    c.onclick = function () {
      if (this.checked == true) {
        synchronousFile(this.id)
      }
    }
  }

</script>

</html>

问题:当我们选中 3 个 checkbox 的时候,依次往服务器发送了 3 次同步文件的请求。如此频繁的网络请求将会带来相当大的开销

解决方案:我们可以通过一个代理函数 proxySynchronousFile 来收集一段时间之内的请求, 最后一次性发送给服务器。比如我们等待 2 秒之后才把这 2 秒之内需要同步的文件 ID 打包发给 服务器,如果不是对实时性要求非常高的系统,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
虚拟代理在惰性加载中的应用

略。

缓存代理用于ajax异步请求数据

略。

用高阶函数动态创建代理
/**************** 计算乘积 *****************/
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

其他代理模式

  • 防火墙代理:控制网络资源的访问,保护主题不让“坏人”接近。

  • 远程代理:为一个对象在不同的地址空间提供局部代表,在 Java 中,远程代理可以是另
    一个虚拟机中的对象。

  • 保护代理:用于对象应该有不同访问权限的情况。

  • 智能引用代理:取代了简单的指针,它在访问对象时执行一些附加操作,比如计算一个 对象被引用的次数。

  • 写时复制代理:通常用于复制一个庞大对象的情况。写时复制代理延迟了复制的过程, 当对象被真正修改时,才对它进行复制操作。写时复制代理是虚拟代理的一种变体,DLL (操作系统中的动态链接库)是其典型运用场景。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值