《js 设计模式与开发实践》读书笔记 6


  代理模式是为一个对象提供一个代用品或占位符,以便控制对它的访问。生活中也可以找到很多代理模式的场景。比如:明星都有经纪人作为代理。如果想要请明星办一场演出,只能联系他的经纪人。经纪人会把商业演出的细节和报酬谈好后,把合同交给明星。代理模式的关键是,当客户不方便直接访问一个对象时或者不满足需要时候,提供一个替身对象来控制对这个对象的访问。

   举个例子,小明追女孩 A,因为内向想通过他们共同的好友 B 来代替自己完成送花这件事。

var Flower = function () {}

var xiaoming = {
  sendFlower: function (target) {
    var flower = new Flower()
    target.receiveFlower(flower)
  }
}

var A = {
  receiveFlower: function (flower) {
    console.log('收到花' + flower)
  }
}

xiaoming.sendFlower(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 在心情好的时候收到花,小明表白成功的几率有 60%,而当 A 在心情差的时候收到花,表白成功的几率无限趋近于 0。这时小明刚和 A 认识两天,还无法辨别 A 什么时候心情好。如果不合时宜的把花送给 A,花被直接扔掉的可能性大!但 A 的朋友 B 却很了解 A,所以小明只管把花交给 B,B 会监听 A 的心情变化,然后选择 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.receiveFlower(flower)
    })
  }
}

var A = {
  receiveFlower: function (flower) {
    console.log('收到花' + flower)
  },
  listenGoodMood: function (fn) {
    setTimeout(function () {
      fn()
    }, 10000)
  }
}

xiaoming.sendFlower(B)

  这个虚拟例子里有两种代理模式的影子。一种是保护代理。代理 B 可以帮助 A 过滤掉一些请求。另外一个是虚拟代理,假设现实中花价格不菲,导致 new Flower 是一个很昂贵的操作,那么我们可以把 new flower 的操作交给代理 b,代理 b 会在 a 心情好的时候在执行 new flower.虚拟代理把一些开销很大的对象,延迟到真正需要它的时候才去创建。

var Flower = function () {}

var xiaoming = {
  sendFlower: function (target) {
    target.receiveFlower()
  }
}

var B = {
  receiveFlower: function () {
    A.listenGoodMood(function () {
      var flower = new Flower()
      A.receiveFlower(flower)
    })
  }
}

var A = {
  receiveFlower: function (flower) {
    console.log('收到花' + flower)
  },
  listenGoodMood: function (fn) {
    setTimeout(function () {
      fn()
    }, 10000)
  }
}

xiaoming.sendFlower(B)

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

var myImage = (function () {
  var imgNode = document.createElement('img')
  document.body.appendChild(imgNode)

  return {
    setSrc: function (src) {
      imgNode.src = src
    }
  }
})()

myImage.setSrc(
  'https://th.bing.com/th/id/OIP.UnMxcvVpoCXDQAmeoQoujQHaEH?w=304&h=180&c=7&r=0&o=5&dpr=2&pid=1.7'
)

  我们把网速调制低网速,可以看到,在图片加载好之前,有段空白时间。我们引入代理对象 proxyImage,通过这个代理对象,在图片被真正加载好之前,页面中将出现一张占位的菊花图。来提示图片正在加载。

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(
        'https://th.bing.com/th/id/OIP.5LYyPhnlf2dhsc5Ht9Mp4AHaHa?pid=ImgDet&rs=1'
      )
      img.src = src
    }
  }
})()

proxyImage.setSrc(
  'http://5b0988e595225.cdn.sohucs.com/images/20180726/9bf801e24fe944a7a02737a715dabc22.jpeg'
)

  也许会有疑问,不过实现一个图片加载问题,不用任何模式都可以办到,那么引入代理模式的好处在哪里呢?我们先写一个常见的加载函数。

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 =
        'https://th.bing.com/th/id/OIP.5LYyPhnlf2dhsc5Ht9Mp4AHaHa?pid=ImgDet&rs=1'
      img.src = src
    }
  }
}

MyImage.setSrc(
  'http://5b0988e595225.cdn.sohucs.com/images/20180726/9bf801e24fe944a7a02737a715dabc22.jpeg'
)

  这个里面会涉及一个面向对象设计的原则:单一职责原则。单一职责指的是,就一个方法而言,应该仅有一个引起它变化的原因。如果一个对象承担了多项职责,就意味着这个对象变得巨大,引起它变化的原因可能会有多个。职责被定义为引起变化的原因。上段代码中的 MyImage 对象除了负责给 img 节点设置 src 外,还要负责预先加载图片。我们在处理其中一个职责时,有可能因为其强耦合性影响另外一个职责的实现。

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

  很多公司要写周报,这周你做了什么,交给总监审批。总监手下有 150 个人,假如每个人都直接发给他,他需要一天的时间来看。所以我们把周报发给各自的组长,组长作为代理,把组内成员的周报合并提炼一份发给总监,这样以来,总监的邮箱就清净多了。在程序中。比如我们做一个文件同步,我们选中一个 checkbox,它对应的文件就被同步到另外一台备用服务器上。

<body>
  <input type="checkbox" id="1" />1 <input type="checkbox" id="2" />2
  <input type="checkbox" id="3" />3 
  <input type="checkbox" id="4" />4

  <script>
    var fetchFile = function (id) {
      console.log('同步文件,id: ' + id)
    }

    var checkbox = document.getElementsByTagName('input')

    for (var i = 0; i < checkbox.length; i++) {
      var c = checkbox[i]
      c.onclick = function () {
        if (this.checked === true) {
          fetchFile(this.id)
        }
      }
    }
  </script>
</body>

  给他绑上点击事件。点击的同时向服务器同步文件。这个实现我们点击多少次执行多少次。如此频繁的网络请求会带来大的开销。解决方案是,我们通过一个代理函数。收集一段时间之内的请求,最后一次性发送给服务器。比如我们等待两秒之后把需要同步的文件发给服务器。

<body>
  <input type="checkbox" id="1" />1 <input type="checkbox" id="2" />2
  <input type="checkbox" id="3" />3 <input type="checkbox" id="4" />4

  <script>
    var fetchFile = function (id) {
      console.log('同步文件,id: ' + id)
    }

    var proxyFetchFile = (function () {
      var cache = [],
        timer

      return function (id) {
        cache.push(id)
        if (timer) {
          return
        }
        timer = setTimeout(function () {
          fetchFile(cache.join(','))
          clearTimeout(timer)
          timer = null
          cache.length = 0
        }, 2000)
      }
    })()

    var checkbox = document.getElementsByTagName('input')

    for (var i = 0; i < checkbox.length; i++) {
      var c = checkbox[i]
      c.onclick = function () {
        if (this.checked === true) {
          proxyFetchFile(this.id)
        }
      }
    }
  </script>
</body>

   代理模式有很多小分类,我们在编写业务代码的时候,可以考虑当前业务是否真的不方便直接访问某个对象的时候,在编写也不迟。

posted @ 2022-09-16 11:10  艾路  阅读(18)  评论(0编辑  收藏  举报