|
| 1 | +# Proxy(代理模式) |
| 2 | + |
| 3 | +Proxy(代理模式)属于结构型模式,通过访问代理对象代替访问原始对象,以获得一些设计上的便捷。 |
| 4 | + |
| 5 | +**意图:为其他对象提供一种代理以控制这个对象的访问。** |
| 6 | + |
| 7 | +## 举例子 |
| 8 | + |
| 9 | +如果看不懂上面的意图介绍,没有关系,设计模式需要在日常工作里用起来,结合例子可以加深你的理解,下面我准备了三个例子,让你体会什么场景下会用到这种设计模式。 |
| 10 | + |
| 11 | +### 获得文本对象长度 |
| 12 | + |
| 13 | +获得一个文本对象长度,必须要真正渲染出来,而渲染是比较耗时的,我们可能只在某些场景下需要访问文本对象长度,而更多时候只需要读取文本内容,这两种操作耗时是完全不同的,如何做到业务层调用无感知,来优化执行耗时呢? |
| 14 | + |
| 15 | +代理模式可以解决这个问题,我们将业务层使用的文本对象替换为代理对象,这个代理对象初始化并不渲染文本,而是在调用文本长度时才渲染。 |
| 16 | + |
| 17 | +### 对象访问保护 |
| 18 | + |
| 19 | +某个大型系统开发完了,突然要求增加代码访问权限体系,不同模块对相同的底层对象拥有不同访问权限,此时这个权限控制逻辑如果写入底层对象,就违背了开闭原则,而对象本身的实现也不再纯粹,增加了维护成本,如何做到不修改对象本身,实现权限控制呢? |
| 20 | + |
| 21 | +代理模式也能解决,将底层对象导出替换为代理对象,由代理对象控制访问权限即可。 |
| 22 | + |
| 23 | +### 对象与视图双向绑定 |
| 24 | + |
| 25 | +Angular 或 Vue 这类前端框架采用双向绑定视图更新技术,即对象修改后,使用到的视图会自动刷新,这就需要做到以下两点: |
| 26 | + |
| 27 | +1. 在对象被访问时,记录调用的视图绑定。 |
| 28 | +2. 在对象被修改时,刷新调用它的视图。 |
| 29 | + |
| 30 | +问题是,在业务代码使用对象与修改对象的地方插入这段逻辑,显然会增加巨大的维护成本,如何做到业务层无感知呢? |
| 31 | + |
| 32 | +代理模式可以很好的解决这个问题,其实业务层拿到的对象已经是代理对象了,它在被访问与被修改时,都会执行固定的钩子做视图绑定与视图刷新。 |
| 33 | + |
| 34 | +## 意图解释 |
| 35 | + |
| 36 | +**意图:为其他对象提供一种代理以控制这个对象的访问。** |
| 37 | + |
| 38 | +代理模式的意图很容易理解,就是通过代理对象代替原始对象的访问。 |
| 39 | + |
| 40 | +这只是代理模式的实现方式,代理模式真正的难点不在于理解它是如何工作的,而是理解哪些场景适合用代理,或者说创建了代理对象,怎么用才能发挥它的价值。 |
| 41 | + |
| 42 | +在上面例子中,已经举出了几种常见代理使用场景: |
| 43 | + |
| 44 | +1. 对开销大的对象使用代理,以按需使用。 |
| 45 | +2. 对需要保护的对象进行代理,在代理层做权限控制。 |
| 46 | +3. 在对象访问与修改时要执行一些其他逻辑,适合在代理层做。 |
| 47 | + |
| 48 | +## 结构图 |
| 49 | + |
| 50 | +<img width=600 src="https://img.alicdn.com/imgextra/i3/O1CN01eZHGHQ28t0oeHYzas_!!6000000007989-2-tps-1262-522.png"> |
| 51 | + |
| 52 | +使用时关系如下: |
| 53 | + |
| 54 | +<img width=600 src="https://img.alicdn.com/imgextra/i4/O1CN01iwyMKQ1KbOnR0N2AP_!!6000000001182-2-tps-1270-206.png"> |
| 55 | + |
| 56 | +Subject 定义的是 RealSubject 与 Proxy 共用的接口,这样任何使用 RealSubject 的地方都可以使用 Proxy。 |
| 57 | + |
| 58 | +RealSubject 指的是原始对象,Proxy 是一个代理实体。 |
| 59 | + |
| 60 | +关系图中可以看出,当客户端要访问 subject 时,第一层访问的是 Proxy 代理,由这个代理将 realSubject 转发给客户端。 |
| 61 | + |
| 62 | +## 代码例子 |
| 63 | + |
| 64 | +下面例子使用 typescript 编写。 |
| 65 | + |
| 66 | +```typescript |
| 67 | +// 对象 obj |
| 68 | +const proxy = new Proxy(obj, { |
| 69 | + get(target,key) {} |
| 70 | + set(target,key,value) {} |
| 71 | +}) |
| 72 | +``` |
| 73 | + |
| 74 | +JS 创建代理还是蛮简单的,代理可以控制对象的所有成员属性,包括成员变量与成员方法的访问(get)与修改(set)。 |
| 75 | + |
| 76 | +## 弊端 |
| 77 | + |
| 78 | +代理模式会增加微弱的开销,因此请不要将所有对象都变成代理,没有意义的代理只会徒增程序开销。 |
| 79 | + |
| 80 | +另外代理对象过多,也会导致调试困难,因为代理层的存在,我们往往可能忽略这一层带来的影响,导致忘记这个对象其实是一个代理。 |
| 81 | + |
| 82 | +## 总结 |
| 83 | + |
| 84 | +代理和继承有足够多的相似之处,继承中,子类几乎可以人为是对父类的代理,子类可以重写父类的方法。但代理和继承还是有区别的: |
| 85 | + |
| 86 | +如果你没有采用 `new Proxy` 这种 API 创建代理,而是采用继承的方式实现,你会一下子继承这个类的所有方法,而做不到按需控制访问权限的灵活效果,所以代理比继承更加灵活。 |
| 87 | + |
| 88 | +JS 的 `new Proxy` 对应了 Java 动态代理模式,一般认为动态代理比静态代理更强大。 |
| 89 | + |
| 90 | +最后,还要重申那句话,代理模式理解与运用并不难,难就难在能否在恰当的场合想到它,双向绑定几乎是代理模式最好的例子。 |
| 91 | + |
| 92 | +> 讨论地址是:[精读《设计模式 - Proxy 代理模式》· Issue #291 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/291) |
| 93 | +
|
| 94 | +**如果你想参与讨论,请 [点击这里](https://github.com/dt-fe/weekly),每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。** |
| 95 | + |
| 96 | +> 关注 **前端精读微信公众号** |
| 97 | +
|
| 98 | +<img width=200 src="https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg"> |
| 99 | + |
| 100 | +> 版权声明:自由转载-非商用-非衍生-保持署名([创意共享 3.0 许可证](https://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh)) |
0 commit comments