@TOC
代理模式
- 介绍
- 演示
- 场景
- 总结
代理模式 介绍
-
使用者无权访问目标对象
-
中间加代理,通过代理做授权和控制
概念
为其他对象提供一种代理以控制对这个对象的访问。在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。
示例
- 科学上网,访问github.com
- 明星经纪人,假设我们想邀请一位明星,那么并不是直接连接明星,而是联系明星的经纪人,来达到同样的目的
另外,想一下你通过链家的中介买房子,算不算代理模式?—— 不算,此时是三方关系,买家、中介、卖家,合同上都要签字的。而上面的明星经纪人却是两方关系,你和经纪人联系就好了,你不用跟明星直接联系。
代理模式 演示
常见的 UML 类图是
简化之后
代码描述
class ReadImg {
constructor(fileName) {
this.fileName = fileName
this.loadFromDisk() // 初始化即从硬盘中加载
}
display() {
console.log(`display...` + this.fileName)
}
loadFromDisk() {
console.log(`loading... ` + this.fileName)
}
}
class ProxyImg {
constructor(fileName) {
this.realImg = new ReadImg(fileName)
}
display() {
this.realImg.display()
}
}
// 测试代码
let proxImg = new ProxyImg('1.png')
proxImg.display()
关键在于两者都必须用display这一个 API 名字,就像你用不用科学上网,访问youtube.com的网站都是一样的。
代理模式 场景
不一定会严格按照 UML 类图来实现,但是这种设计思想是能体现出来的。
网页事件代理
将目标元素的点击时间,代理到父元素上,以实现某种功能,很常用。
<div id="div1">
<a href="#">a1</a>
<a href="#">a2</a>
<a href="#">a3</a>
<a href="#">a4</a>
</div>
<button>点击增加一个 a 标签</button>
<script>
var div1 = document.getElementById('div1')
div1.addEventListener('click', function (e) {
var target = e.target
if (e.nodeName === 'A') {
alert(target.innerHTML)
}
})
</script>
jQuery $.proxy
$('#div1').click(function () {
// this 符合期望
$(this).addClass('red')
})
$('#div1').click(function () {
setTimeout(function () {
// this 不符合期望
$(this).addClass('red')
}, 1000);
});
// 可以用如下方式解决
$('#div1').click(function () {
var _this = this
setTimeout(function () {
// _this 符合期望
$(_this).addClass('red')
}, 1000);
});
// 但推荐使用 $.proxy 解决,这样就少定义一个变量
$('#div1').click(function () {
setTimeout($.proxy(function () {
// this 符合期望
$(this).addClass('red')
}, this), 1000);
});
总结一下,$.proxy(fn, obj)返回的就是fn的代理,这个代理和fn的功能一样,只不过将this设置成了我们期望值。虽然看似简单、常用的功能,但是用代理模式设计的。
ES6 Proxy
Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。就是一个属性的代理器。
// 明星
let star = {
name: '张XX',
age: 25,
phone: '13910733521'
}
// 经纪人
let agent = new Proxy(star, {
get: function (target, key) {
if (key === 'phone') {
// 返回经纪人自己的手机号
return '18611112222'
}
if (key === 'price') {
// 明星不报价,经纪人报价
return 120000
}
return target[key]
},
set: function (target, key, val) {
if (key === 'customPrice') {
if (val < 100000) {
// 最低 10w
throw new Error('价格太低')
} else {
target[key] = val
return true
}
}
}
})
// 主办方
console.log(agent.name)
console.log(agent.age)
console.log(agent.phone)
console.log(agent.price)
// 想自己提供报价(砍价,或者高价争抢)
agent.customPrice = 150000
// agent.customPrice = 90000 // 报错:价格太低
console.log('customPrice', agent.customPrice)
代理模式 总结
- 什么是代理模式
- 关键:和目标接口一致,不能改变接口
- 前端使用场景
对比:
- vs 适配器模式:
- 适配器模式:提供一个不同的接口(如不同版本的插头)
- 代理模式:提供一模一样的接口,用不用代理,访问 facebook 的网址都一样
- vs 装饰器模式
- 装饰器模式:扩展功能,原有功能不变且可直接使用;使用方也可直接使用目标接口。
- 代理模式:显示原有功能,提供给使用方的是经过限制或者阉割之后的功能;使用方不可直接使用目标接口。
设计模式验证:
- 将代理类和目标类进行分离,用代理隔离开目标类和使用者
- 符合开放封闭原则