[设计模式]——代理模式

190 阅读4分钟

定义

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

比如说,明星都有经纪人作为代理,如果想请明星来办一场商业演出,只能联系他的经纪人。经纪人会把商业演出的细节和报仇都谈好之后,再把合同交给明星签。

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

虚拟代理

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

虚拟代理实现图片预加载

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


首先创建一个普通的本地对象,这个对象负责往页面中创建一个img标签,并且对外提供setSrc接口,外界调用这个接口,便可以给img设置src

class MyImage {
  constructor() {
    this.img = document.createElement('img');
    document.body.appendChild(this.img);
  }
  setSrc(src) {
    this.img.src = src;
  }
}

执行const myImage=new MyImage()生成一个img节点插入body中,然后执行setSrc('xxx'),这样的常规写法,在图片被加载好之前,页面会有一段长时间的空白期。

现在引入代理对象ProxyImage类,提供这个代理对象,在图片被真正加载好之前,页面中将会出现一张占位的加载图。

class ProxyImage {
  constructor() {
    this.myImage = new MyImage();
    this.image = new Image();
    this.image.onload = function () {
      this.myImage.setSrc(this.image.src);
    }
  }
  setSrc(src) {
    this.myImage.setSrc('./loading.gif');
    this.image.src = src;
  }
}
const proxyImage = new ProxyImage();
proxyImage.setSrc(xxxx);

提供ProxyImage代理类间接地访问MyImage类实例化生成的对象,ProxyImage控制了对MyImage实例化对象的访问,并且在此过程中加入了一些额外的操作,在图片真正加载好之前去设置一张本地的loading图。

代理的意义

假如不使用虚拟代理来实现图片的预加载功能,代码如下:

class MyImage {
  constructor() {
    this.img = document.createElement('img');
    document.body.appendChild(this.img);
    this.image = new Image();
    image.onload = function () {
      this.img.src = this.image.src;
    };
  }
  setSrc(src) {
    this.img.src = './loading.gif';
    this.image.src = src;
  }
}

上段代码中,MyImage类中除了负责生成img节点并设置src,还要负责预加载图片。这样的写法违背了单一职责原则。

另外,假如当图片非常小时,根本不需要预加载,使用预加载还会出现占位图片一闪而过的问题,如果这个时候重新再写一个MyImage类或者去修改原来的代码,还会违背开放-封闭原则。


实际上MyImage类的功能只需要给img节点设置src,预加载图片只是一个锦上添花的功能,假如把这个功能放在代理中,那么代理的作用在这里就体现了,代理负责预加载图片,预加载的操作完成之后,把请求重新交给本体MyImage去设置src。

这里没有改变或增加MyImage功能,只是通过代理,给这个类进行了扩展。

给img节点设置src和图片预加载这两个功能,被隔离在了两个类中,它们各自变化而不影响对方。假如有一天不需要预加载,那么只需要请求改成本体而不是请求代理对象即可。

缓存代理

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

使用缓存代理进行计算乘积

高阶函数可以简单理解为把一个函数作为参数传入另一个函数中。这里使用高阶函数动态创建缓存代理,就是实现一个专门用于创建缓存代理的工厂函数,把要代理的函数传入到这个工厂函数中,返回一个代理函数。

const createProxyFactory = (fn) =>{
  let cache = {};
  return function () {
    const args = Array.prototype.join.call(arguments, ',');
    if (args in cache) {
      return cache[args];
    }
    return cache[args] = fn.apply(this, arguments);
  }
};

实现一个mult函数

const mult = function () {
  let a = 1;
  for (let i = 0, l = arguments.length; i < l; i++) {
    a = a * arguments[i];
  }
  return a;
};

let proxyPlus=createProxyFactory(mult)
proxyPlus(1,2,3,4)
proxyPlus(1,2,3,4) //第二次不用再计算,直接从缓存中获取

mult函数当作参数传入createProxyFactory中,即可实现mult缓存代理。