我们来回顾上一章策略模式留下的问题:
假设公司的绩效等级分为 A,B,C,D,E 年终奖对应的绩点为 2,1.8,1.5,1.1,0.8。假设年终基数为 base,年终奖为:年终基数 * 绩点。已知绩效等级,求员工的年终奖。
// 绩效等级和绩点的映射关系
const levelToCredit = {
A: 2,
B: 1.8,
C: 1.5,
D: 1.1,
E: 0.8
}
// 年终奖计算逻辑
function getsalary(base, level) {
return levelToCredit[level] * base
}
代理模式,有点像经纪人的角色,可以完成非核心事务。假设我们要使用一个函数在页面加载一张图片,代码应该是这样的:
var imgNode = (function () {
const imgNode = document.createElement('img')
document.body.appendChild(imgNode)
return {
setSrc: function(src) {
imgNode.src = src
}
}
})()
imgNode.setSrc('https://img.com/banner.png') // 加载图片
事实上,我们调用 setSrc 的时候,会发起一个网络请求,在图片资源返回前,图片是空白的,为了避免图片返回的时候闪烁,我们为图片增加 loading 的情况。假设一开始直接修改上述代码:
var imgNode = (function () {
// 真正展示图片的节点
const imgNode = document.createElement('img')
document.body.appendChild(imgNode)
// 代理节点
var proxyImg = document.createElement('img')
// 当代理图片加载完成的时候,把图片的链接赋值给展示节点
proxyImg.onload = function() {
imgNode.setSrc(this.src)
}
return {
setSrc: function(src) {
// 预先loading
imgNode.src = 'https://img.com/loading.gif'
// 代理节点开始请求图片数据
proxyImg.src = src
}
}
})()
imgNode.setSrc('https://img.com/banner.png') // 加载图片
这样的代码相信是很多人的第一选择,包括我开始想到的方案也是这样的,那这样的代码有什么缺点呢?
在未来假设网速快到极致,这样 loading 占位的图片反而导致页面闪烁,这时候如果想把代理的方式去掉,就要修改函数的逻辑,那这样就违反了开放封闭的原则。
并且代理和创建节点,这两个职责耦合到了一个函数,也违反单一职责原则。不过大家现在可能仅仅知道我们要遵守这些原则,至于为什么,比较少人考究,也很少文章详细解释为什么。
以我个人的开发经验来说,可以这么理解:
假设某个需求里面有 A 和 B 两个功能,B 功能是帮 A 做一些事;小王同学把这两个功能都写在了函数 F 里,某一天,产品要砍掉 B 这个功能,然后这时小王离职了,小李接替他的工作来砍掉B,小李看完代码发现砍掉 B 得修改整个 F 函数,小李不仅要看懂函数 F,还要把 B 功能从 F 里面剔除,并且最后还要保证不影响 A 功能。这么一顿操作,小李感觉整个人都不好了,因为他只是想砍掉 B。
不幸的是他还要同时了解 A 功能,F 函数的代码接口以及修改。这样的开发模式在大型应用里面时非常致命的,容易导致整个项目稳定性过差,并且开发的代码容易成为相互的心智负担。
假设我们使用代理模式把 B 分离出去,回到上面的例子,首先我们是没有代理的代码,imgNode 函数负责生成一张图片:
var imgNode = (function () {
// 创建展示的图片节点
const imgNode = document.createElement('img')
// 挂载到body
document.body.appendChild(imgNode)
// 暴露setSrc
return {
setSrc: function(src) {
imgNode.src = src
}
}
})()
var proxyImg = (function(){
// 图片节点
let node = null
// 创建代理节点
const proxyImg = document.createElement('img')
// 图片加载完成 把node设置为代理图片的src
proxyImg.onload = function() {
node.setSrc(this.src)
}
return (imgNode) => {
return {
setSrc: function(src) {
// 使用代理图片节点请求
proxyImg.src = src
// 缓存 node
node = imgNode
// 把展示图片节点的图片设置为 loading
img.setSrc('https://img.com/loading.png')
}
}
}
})()
const img = proxyImg(imgNode)
img.setSrc('https://img.com/banner.png')
这里 proxyImg 这个函数完全是一个锦上添花的功能,假设未来网速快到一定程度,这种 loading 可以淘汰,那我们不需要代理也能正常使用,把 img.setSrc
换成 imgNode.setSrc
实现功能。那么代理模式有什么特点呢,首先实现了和原功能一样的 api 这样在取消使用代理模式的时候也不会因为 api 的差异性而进行大改。
网友评论