对于一个类而言,应该只有一个引发它变化的缘由。在JavaScript中,单一职责原则更多地是被运用在对象或者方法级别上。单一职责原则(SRP)的职责定义为“引发变化的缘由”,若是咱们有多个动机去改写一个方法,那这个方法就对应多个职责。若是一个方法承担了过多职责,在需求的变迁过程当中,须要改写这个方法的可能性就越大。所以咱们能够总结SRP原则:一个对象或者方法只作一件事。javascript
SRP原则在不少模式中有着普遍的应用,例如代理模式、装饰者模式等。
代理模式
在我写的代理模式一文中,有一个图片预加载的例子,经过增长虚拟代理的方式,把图片预加载的职责放到代理对象中,而本体仅仅负责往页面添加img标签。
myImage负责往页面添加img标签:html
var myImage = (function () {
var imgNode = document.createElement('img')
document.body.appendChild(imgNode)
return {
setSrc (src) {
imgNode.src = src
}
}
})()
复制代码
proxyImage负责预加载图片,并加载完图片后把请求交给本体myImage:java
var proxyImage = (function () {
var img = new Image()
img.onload = function () {
myImage.setSrc(this.src)
}
return {
setSrC (src) {
myImage.setSrc('loading.gif')
img.src= src
}
}
})()
proxyImage.setSrc('http://xxx.com/01.jpg')
复制代码
这样把向页面添加img标签的功能和预加载图片的职责分开放到两个对象中,每一个对象只有一个被修改的意图,并且修改其中一个对象也不会影响另外一个对象。
装饰者模式
使用装饰者模式的时候,咱们一般让类或者对象只有一些基础的职责,更多的职责在代码运行时被动态地装饰到对象上,这也是分离职责的一种方式。
在装饰者模式这篇文章中,咱们把数据上报的功能单独放在一个函数里,而后把这个函数动态地装饰到业务函数上:git
Function.prototype.after = function (fn) {
var self = this
return function () {
var ret = _self.apply(this, arguments)
fn.apply(this, arguments)
return ret
}
}
var showLogin = function () {
console.log('打开登陆弹窗')
}
var log = function () {
console.log('上报数据')
}
document.getElementById('loginBtn').onclick = showLogin.after(log)
复制代码
首先明确一点,并非全部的职责都应该一一分离。
若是随着需求的变化,有两个职责老是同时变化,那就不分离他们。好比在ajax请求的时候,建立xhr对象和发送xhr请求几乎都是一块儿的,那么这两个职责就没有必要分离。
职责的变化轴线仅当它们肯定会发生变化时才具备意义,即便两个职责已经被耦合在一块儿,但它们没有发生改变的预兆,也没有必要主动分离它们,等代码重构时分离也不迟。程序员
在人的常规思惟中,老是习惯性地把一组相关行为放到一块儿,如何正确地分离职责不是容易的一件事情。
一方面,咱们接受SRP原则的指导,另外一方面,咱们也没有必要任什么时候候都一成不变地遵照规则。 在实际开发中,由于种种缘由违反SRP原则的状况并很多见。好比jQuery的attr等方法,即负责赋值,又负责取值,这明显违反了SRP原则。对于jQuery维护者来讲,会有必定困难,可是对用户来讲,却简化了api的使用。
在方便性与稳定性之间要有一些取舍,具体是选择方便性仍是稳定性,取决于具体的应用场景。github
SRP的优势是下降了单个类或者对象的复杂度,按照职责把对象分解成更小的粒度,这有助于代码复用和进行单元测试。
它最明显的缺点就是会增长编写代码的难度。其次,当咱们按职责把对象分解成小的粒度以后,实际上也增长了对象之间互相联系的难度。web
最少知识原则(LKP)说的是一个软件实体应当尽可能减小与其它实体发生相互做用,在面向对象中,指的就是在程序设计的时候,应当尽可能减小对象之间的交互。若是两个对象没必要直接通讯,那么这两个对象就不要发生直接的相互联系。ajax
最少知识原则在设计模式中体现最多的是中介者模式和外观模式,可是外观模式在JavaScript中不多用,因此这里就不介绍了。
中介者模式
在中介者模式一文中,咱们经过一个泡泡糖游戏的例子来学习中介者模式。当游戏有成千上万的玩家对战的时候,若是经过玩家互相引用达到通知游戏状态的目的,那实现起来代码将没法维护。可是经过引入一个中介者的方式,解耦全部玩家之间的直接联系,当一个玩家的状态改变时,只须要经过中介者对象来通知便可。
封装在最少知识原则中的体现
封装在很大程度上表达的是数据隐藏,一个模块或者对象将内部的数据或者实现细节隐藏起来,只暴露必要的接口给外界访问。对象之间不免产生联系,当一个对象必须引用另外一个对象,经过只暴露必要的接口从而让对象之间的联系限制在最小的范围以内。设计模式
在面向对象程序中,开放-封闭原则是最重要的一条原则。不少时候,一个程序具备良好的设计,它一般是符合开放-封闭原则的。开放封闭原则指的就是:一个对象(类、函数、模块)等应该是能够扩展的,可是不可修改。api
假如咱们在维护一个大型的web项目,这个项目已经有必定的历史,也有不少人维护,代码已经有十万行。这时候,你接到一个需求,须要在window.onload以后,上报必定的数据。这个对开发来讲固然没什么难度,因而打开页面代码加上一行:
window.onload = function () {
log('上报数据')
}
复制代码
在需求变动的过程当中,咱们常常是找到相关代码,而后修改它,这彷佛是理所固然的。可是若是想象一下,目前的window.onload函数是一个有几百行代码的巨型函数,里面遍及着各类变量和业务逻辑,若是需求更复杂,就可能会改好一个bug,产生5个bug。因而,咱们经过在AOP来动态地给window.onload增长新功能:
Function.prototype.after = function (fn) {
var self = this
return function () {
var ret = _self.apply(this, arguments)
fn.apply(this, arguments)
return ret
}
}
window.onload = (window.onload || function () {}).after(function () {
// 添加咱们新的业务代码
})
复制代码
经过动态装饰函数的方式,咱们彻底不用理会从前window.onload函数的内部实现。
过多的条件分支是形成程序违反开放-封闭原则的一个常见缘由,每当须要增长一个新的if语句时,都被迫要改动原函数。把if换成switch是没有用的,这是一种换汤不换药的作法。实际上,当咱们看到大量的if或者switch语句时,就能够考虑使用对象的动态性来重构它们。
下面是一种反例的实现:
var makeSound = function (animal) {
if (animal instanceof Duck) {
console.log('嘎嘎嘎')
} else if (animal instanceof Chicken) {
console.log('咯咯咯')
}
}
var Duck = function () {}
var Chicken = function () {}
makeSound(new Duck()) // 嘎嘎嘎
makeSound(new Chicken()) // 咯咯咯
复制代码
增长了一种狗的类型,必须修改代码:
var makeSound = function (animal) {
if (animal instanceof Duck) {
console.log('嘎嘎嘎')
} else if (animal instanceof Chicken) {
console.log('咯咯咯')
} else if (animal instanceof Dog) {
console.log('汪汪汪')
}
}
const Dog = function () {}
makeSound(new Dog()) // 汪汪汪
复制代码
利用多态的实现,把程序中不变的部分隔离出来(动物会叫),而后把可变的部分封装起来(不一样的动物发出不一样的叫声),这样程序就有了扩展性:
var makeSound = function (animal) {
animal.sound()
}
var Duck = function () {}
Duck.prototype.sound = function () {
console.log('嘎嘎嘎')
}
var Chicken = function () {}
Chicken.prototype.sound = function () {
console.log('咯咯咯')
}
var Dog = function () {}
Dog.prototype.sound = function () {
console.log('汪汪汪')
}
makeSound(new Duck()) // 嘎嘎嘎
makeSound(new Chicken()) // 咯咯咯
makeSound(new Dog()) // 汪汪汪
复制代码
指导咱们实现开放-封闭原则的规律就是:找出程序中常常发生变化的地方,而后把变化封装起来。
经过封装变化,咱们能够把系统中稳定的部分和容易变化的部分隔离开来,在系统的演变过程当中,咱们只须要替换那些容易变化的部分,由于这部分已经封装好了,因此替换起来也相对容易。
除了利用对象的多态性以外,下面还有一些方式能够帮助咱们编写遵照开放-封闭原则的代码:
var getUserInfo = function (callback) {
$.ajax('http://xxx.com/getUserInfo', callback)
}
getUserInfo(function (data) {
console.log('更新cookie')
})
getUserInfo(function (data) {
console.log('更新我的主页信息')
})
复制代码
在职责链模式中,也许会有人疑问:开放-封闭原则要求咱们只能经过增长源码的方式来扩展程序的功能,而不容许修改源码。当咱们往职责链增长一个新的订单100节点时,也必需要改动链条的代码:
order500.setNextSuccessor(order200).setNextSuccessor(orderNormal)
// 修改:
order500.setNextSuccessor(order200).setNextSuccessor(order100).setNextSuccessor(orderNormal)
复制代码
实际上,让程序保持彻底封闭是很难作到的,就算能作到也须要花太多时间和精力。并且让程序符合开放-封闭原则的代价是引入更多抽象层次,这也会增长代码的复杂度。在有些状况下,咱们不管如何都是作不到彻底封闭的,这时候咱们就要明白下面两点:
引用Bob大叔的《敏捷软件开发原则、模式与实践》:
有句古老的谚语:“愚弄我一次,应该羞愧的是你。再次愚弄我,应当羞愧的是我。”这也是一种有效对待软件设计的态度。为了防止软件背着没必要要的复杂性,咱们容许本身被愚弄一次。
让程序一开始就尽可能遵照开放-封闭原则,并非一件容易的事情。首先,咱们须要知道程序哪些地方会发生变化,这要求咱们能提早预想到未来的一些需求变化。其次,留给开发程序员的需求开发周期是有限的,因此咱们能够说服本身接受不合理代码的第一次愚弄。在需求开发的时候,咱们能够先迅速完成需求,而后再回头找出变化的地方封装起来。