单例模式
定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
var getSingle = function(fn){
var instance
return function(){
return instance || (instance = fn.apply(this, arguments))
}
}
var bindEvent = function(){
document.getElementById('div1').addEventListener = function(){
console.log('click')
}
}
bindEvent = getSingle(bindEvent)
创建对象和管理单例的职责被分布在两个不同的方法中,这两个方法组合起来才具有单例模式的威力。
策略模式
定义:定义一系列算法,把它们一个个封装起来,并且使它们可以相互替换
下面有一个计算奖金的方法:
var calculateBonus = function(level, salary){
if(level==="S"){
return salary * 4
}
if(level==="A"){
return salary * 3
}
if(level==="B"){
return salary * 2
}
return salary
}
calculateBonus("B", 20000)
calculateBonus("S", 6000)
上述函数的缺点很明显:
- calculateBonus 函数比较庞大,包含了很多if-else语句,这些语句需要覆盖所有的逻辑分支。
- calculateBonus 函数缺乏弹性,如果新增了一种新的绩效C,或者想把绩效S的奖金系数改成5,那我们就必须深入calculateBonus函数的内部实现,这是违反开放-封闭原则的。
- 算法的复用性差,如果在程序的其他地方需要重用这些计算奖金的算法呢?我们的选择只有复制粘贴。
我们可以使用策略模式来重构代码。将不变的部分和变化的部分隔开,策略模式的目的就是将算法的使用和算法的实现分离开来。
一个基于策略模式的程序至少由两部分组成。第一部分是一组策略类。策略类封装了具体的算法,并负责具体的计算过程。第二部分是环境类Context,Context接受客户的请求,随后把请求委托给某一个策略类。要做到这一点,说明Context中要维持对某个策略对象的引用。
定义一系列算法,把它们各自封装成策略类,算法被封装在策略类内部的方法里。在客户对Context发起请求的时候,Context总是把请求委托给这些策略对象中间的某一个进行计算。
var strategies = {
"S": function(salary){
return salary * 4
},
"A": function(salary){
return salary * 3
},
"B": function(salary){
return salary * 2
}
}
var calculateBonus = function(level, salary){
return strategies[level](salary)
}
console.log(calculateBonus("S", 20000))
console.log(calculateBonus("A", 10000))
如果现在想增加一个C的奖金策略,只需要在strategies内添加C就可以了
通过使用策略模式重构代码,我们消除了原程序中大片的条件分支语句。所有跟计算奖金有关的逻辑不再放在Context中,而是分布在各个策略对象中。Context并没有计算奖金的能力,而是把这个职责委托给了某个策略对象。每个策略对象负责的算法已被各自封装在对象内部。当我们对这些策略对象发出 “计算奖金” 的请求时,它们会返回各自不同的计算结果,这正是对象多态性的体现,也是 “它们可以相互替换” 的目的。替换Context中当前保存的策略对象,便能执行不同的算法来得到我们想要的结果。
策略模式的优点:
- 策略模式利用组合、委托和多态等技术和思想,可以有效避免多重条件选择语句。
- 策略模式提供了对开放-封闭原则的完美支持,将算法封装在独立的strategy中,使得它们易于切换,易于理解,易于扩展。
- 策略模式中的算法也可以复用在系统的其他地方,从而避免许多重复的复制粘贴工作。
- 在策略模式中利用组合和委托来让Context拥有执行算法的能力,这也是继承的一种更轻便的替代方案。
策略模式的缺点:strategy要向客户暴露它的所有实现,这是违反最少知识原则的。
代理模式
定义:代理模式为一个对象提供一个代用品或占位符,以便控制对它的访问。
代理可以控制、过滤访问,这种称作保护代理
如果操作的过程要new一个对象Flower,而Flower对象需要等到被访问者时机合适的时候做一些事情,如果new Flower会产生比较大的开销,这时就可以交给代理去做。在操作过程中不会马上去执行new Flower,代理者会等到真正的被访问者准备好了的时候,去执行new Flower,然后调用真正的被访问者。这叫做虚拟代理。虚拟代理把一些开销很大的对象,延迟到真正需要它的时候才会去创建。
虚拟代理实现图片预加载
在web开发中,图片预加载是一种常用的技术,如果直接给某个img标签节点设置src属性,由于图片过大或者网络不佳,图片的位置往往有段时间会是一片空白。常见的做法是先用一张loading图片占位,然后用异步的方式加载图片,等图片加载好了再把它填充到img节点里,这种场景就很适合虚拟代理。
// 设置页面中真正显示图片的src(真正要被访问的对象)
var myImage = (function(){
var imgNode = document.createElement('img')
document.body.appendChild(imgNode)
return function(src){
imgNode.src = src
}
})()
var proxyImage = (function(){
var img = new Image()
img.onload = function(){
myImage(this.src)
}
return function(src){
// 代理对象先将src设置为loading图,然后赋值img.src,真正的图片加载完成后,触发onload事件
myImage('xxx/loading.gif')
img.src = src
}
})()
proxyImage('http://xxxxxx.jpg')
纵观整个程序,我们并没有改变或者增加MyImage的接口,但是通过代理对象,实际上给系统添加了新的行为。这是符合开放-封闭原则的。给img节点设置src和图片预加载这两个功能,被隔离在两个对象里,他们可以各自变化而不影响对方。就算有一天我们不再需要预加载,那么不需要改成请求本体而不是请求代理对象即可。
持续更新中....