目录

  1. 什么是代理模式?
  2. 代理模式常见的两种实现方式
  3. 为什么要使用代理而不是直接在源对象做修改呢?
  4. 代理与适配器和装饰器之间的区别
  5. 什么时候使用代理

什么是代理模式?

为一个对象提供一个替身对象(代理对象),以便对于源对象的控制和访问,某些时候我们没法直接引入源对象或者源对象不满足需要时,可以通过替身对象来进行中介和控制。

代理模式主要分为三个角色:客户端(我),代理类,目标类;

客户 ------> 源对象

客户 ------> 代理 -------> 源对象

比如说我有一个业务需求,我需要一个能自动过期数据的一个数据存储对象,那么从所有能存数据的对象中,map是最佳的,因为它有has,size,delete等方便操作的api,而且还能将对象作为key,但是呢,map本身并没有自动删除过期数据的功能,此时我们是不是命中了上面所说的:源对象不满足需要时这个情况。

我们有必要再去造一个数据存储的类吗?没必要吧,有时候也没有这个能力,那么我么就可以通过增加一个代理层的方式,去实现我们的功能,我们代理map对象所有操作方法,在set操作时存储一个时间戳,在get操作的时候判断这个时间戳是否超时,超时就删除,然后再通过映射或者直接操作源对象来返回相应获取结果。

是不是就可以很好的实现这个功能。具体逻辑代码可以参考这个:https://codepen.io/mulingyuer/pen/LYegeVq

代理模式常见的两种实现方式

按照代理创建的时期来区分,可以分为静态代理和动态代理

静态代理

静态代理就是我们手动的去实现一个代理类,需要满足源对象所用到的api,比如有一个对象存在show方法,那么我们也需要手动实现一个名为show的方法。这也是比较简单的逻辑思维。

缺点:由于静态代理是针对某一个类或者对象的,我们不得不手动手动一一实现相对应的接口,当需要代理的东西存在很多个时,就得手动写多个代理类,增加了工作量维护成本,假如有一个对象他有200个接口,工作量就爆炸了,就算你努力写了200个接口的代理,过段时间源对象接口改动了,你还得来代理这再改一遍,刺激的一批。

上面的超时删除的类就是一个静态代理

动态代理

动态代理相对应静态代理,它不需要预先为源对象实现对应的接口,而是在运行时通过某种手段拿到客户调用的接口,从而进行拦截处理。

具体实现js上很难去说明,但是我们es6的Proxy类就是一种动态代理。

const proxy = new Proxy(target,handler);

target为我们的源对象

handler也是一个对象,用来定制拦截行为。

参考链接:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy

动态代理就是对静态代理的一种功能补足,动态在写法上相对于静态会复杂一些。

为什么要使用代理而不是直接在源对象做修改呢?

以上面那个超时删除的类为例子,单独的map对象是不是很纯粹?它只负责存储,而我们超时自动删除是属于业务逻辑,如果map被增加了超时删除的逻辑。

第一是不符合我们的单一职责的设计原则。

第二是当一个对象承担了多项职责,就意味着这个对象将变得巨大,引起它变化的原因就可能会有多个,bug几率也会增大,而且map是最基础的对象,如果改动了,很明显会对整个代码环境产生不可预估的影响。

第三是违反了开放封闭原则,对修改封闭,对扩展开放。

还有一点,当我们书写了一个优秀的代理时,在很久以后业务逻辑可能发生变化,我们不需要这个功能了,此时移除代理,改用源对象并不会导致代码发生很大的改变,只是使用的对象从代理对象转为源对象而已。

代理与适配器和装饰器之间的区别

适配器

适配器是对两个不同的对象进行关联,把一个接口转换为客户端期待的接口,220v的插板是三孔的,手机是usb口的,电源适配器插插线板转成usb给手机用。

适配器不需要实现一样的接口。

装饰器

原来的接口还可以使用,我只是增强了被装饰者的某种功能,并不会改变调用方式。简单来说装饰器模式是增强了原有功能。

区别

代理模式与适配器之间意义上就完全不同,代理是生成一个源对象的代表,而适配器是客户端与目标对象的兼容,进行了一些适配操作。

代理模式和装饰器模式在结构上是差不多,都在内部含有一个源对象的引用,从而可以在任何时候操作目标对象,但是代理模式不一定是增强功能,它可以改变接口的功能。

什么时候使用代理

代理模式有很多种使用场景,但是在平时编程中,往往不需要预先去猜测是否需要使用代理模式,而是当真正发现不方便直接使用某个对象时,再来使用代理模式也不迟。

前端常见的一些使用场景:

  1. 保护代理
  2. 虚拟代理
  3. 缓存代理
  4. 数据响应式

保护代理

我们不希望对象的某些属性被使用,或者不满足要求时被使用,通过代理的方式进行过滤,这种被称为保护代理。

虚拟代理

在程序中往往会有一个代价很昂贵的操作,我们可以设置一个虚拟代理,虚拟代理会在真实调用的时候才执行对应的操作,可以理解为一种预占位,比如我们图片的懒加载。

懒加载会预先使用一张菊花图进行占位,当图片真正被浏览时进行链接替换,触发加载。


class ImgLoad {
  img: HTMLImageElement;
  constructor(img: HTMLImageElement) {
    this.img = img;
  }

  setSrc(src: string) {
    this.img.src = src;
  }
}

class PorxyImgLoad {
  origin: ImgLoad;
  constructor(img: HTMLImageElement) {
    this.origin = new ImgLoad(img);
  }

  setSrc(src: string) {
    this.origin.img.src = "菊花图.gif";

    const img = new Image();
    img.onload = () => {
      this.origin.setSrc(src);
    };
    img.src = src;
  }
}

缓存代理

比如将一些开销比较大的计算结果存储起来,下次再次使用时可以直接读取缓存结果,加快计算速度。一般用的比较少,了解一下就行了。

数据响应式

vue的响应式对象就是通过代理模式实现对象的监听,但是它为了实现通知功能,又整合了发布订阅者模式。

分类: 设计模式 标签: 设计模式代理模式

评论

暂无评论数据

暂无评论数据

目录