单一职责原则

就一个类而言,应该仅有一个引发它变化的缘由。在 JavaScript中,须要用到类的场景并不太多,单一职责原则更多地是被运用在对象或者方法级别上,所以讨论的是大多基于对象和方法。

单一职责原则(SRP)的职责被定义为“引发变化的缘由”。若是咱们有两个动机去改写一
个方法,那么这个方法就具备两个职责。每一个职责都是变化的一个轴线,若是一个方法承担了过
多的职责,那么在需求的变迁过程当中,须要改写这个方法的可能性就越大。
此时,这个方法一般是一个不稳定的方法,修改代码老是一件危险的事情,特别是当两个职
责耦合在一块儿的时候,一个职责发生变化可能会影响到其余职责的实现,形成意想不到的破坏,
这种耦合性获得的是低内聚和脆弱的设计。
所以,SRP原则体现为:一个对象(方法)只作一件事情。

设计模式中的 SRP 原则,SRP原则在不少设计模式中都有着普遍的运用,例如代理模式、迭代器模式、单例模式和装饰者模式。javascript

1. 代理模式

好比如今实现一个图片预加载的例子:你们知道当网速很慢的状况下,把网速调至 5KB/s,能够看到,在图片被加载好以前,页面中有一段长长的空白时间。因此在图片没有加载到客户端以前用一张图片进行站位。 到达预加载的效果。java

var myImage=(function () {
var imgNode =document.createElement("img");
document.body.appendChild( imgNode ); 
var img =new Image();
img.onload=function(){
imgNode.src=img.src;
}

return {
 setSrc: function( src ){
  imgNode.src = './Image00000.jpg';
  img.src=scr;
}
}
})()
myImage.setSrc('https://fkasdjfklas.ydic.com/image');};

这段代码预加载图片函数,有两个职责。一个是预加载职责,一个是加载图片职责。 MyImage 对象除了负责给 img 节点设置 src外,还要负责预加载图片。咱们在处理其中一个职责时,有可能由于其强耦合性影响另一个职责的实现。
且这是违反单一职责原则的。若是一个对象承担了多项职责,就意味着这个对象将变得巨大,引发它变化的缘由可
能会有多个。面向对象设计鼓励将行为分布到细粒度的对象之中,若是一个对象承担的职责过多,
等于把这些职责耦合到了一块儿,这种耦合会致使脆弱和低内聚的设计。当变化发生时,设计可能
会遭到意外的破坏。
若是咱们只是从网络上获取一些体积很小的图片,或者 10年后的网速快到根本再也不需
要预加载,咱们可能但愿把预加载图片的这段代码从 MyImage 对象里删掉。这时候就不得不改动MyImage 对象了。
这时就体现了单一职责好处。经过引入虚拟代理的方式分离其职责,MyImage 对象只负责设置img节点的src属性,虚拟代理对象负责图片的预加载职责。给 img 节点设置 src 和图片预加载这两个功能,被隔离在两个对象里,它们能够各自变化而不影响对方。况且就算有一天咱们再也不须要预加载,那么只须要改为请求本体而不是请求代理对象便可。ajax

这个图片预加载的例子。经过增长虚拟代理的方式,把预加载图片的职责放到代理对象中,而本体仅仅负责往页面中添加 img 标签,这也是它最原始的职责。myImage 负责往页面
中添加 img 标签:编程

var myImage = (function(){
var imgNode = document.createElement( 'img' );
document.body.appendChild( imgNode );
return {
setSrc: function( src ){   
imgNode.src = src;
}
}
})();

proxyImage 负责预加载图片,并在预加载完成以后把请求交给本体 myImage :

var proxyImage = (function(){
var img = new Image;
img.onload = function(){
myImage.setSrc( this.src );
}
return {
setSrc: function( src ){
myImage.setSrc( 'file:// /C:/Users/yeye/Desktop/loading.gif' );
img.src = src;
}
}
})();
    proxyImage.setSrc( 'http:// imgcache.qq.com/music/photo/000GGDys0yA0Nk.jpg' );~

把添加 img 标签的功能和预加载图片的职责分开放到两个对象中,这两个对象各自都只有一个被修改的动机。在它们各自发生改变的时候,也不会影响另外的对象。设计模式

2. 迭代器模式
咱们有这样一段代码,先遍历一个集合,而后往页面中添加一些 div,这些 div的 innerHTML分别对应集合里的元素:

var appendDiv = function( data ){
for ( var i = 0, l = data.length; i < l; i++ ){
var div = document.createElement( 'div' );
div.innerHTML = data[ i ];
document.body.appendChild( div );
}
};
appendDiv( [ 1, 2, 3, 4, 5, 6 ] );

appendDiv 函数原本只是负责渲染数据,可是在这里它还承担了遍历聚合对象 data 的职责。
咱们想象一下,若是有一天 cgi返回的 data 数据格式从 array 变成了 object ,那咱们遍历 data 的
代码就会出现问题,必须改为 for ( var i in data ) 的方式,这时候必须去修改 appendDiv 里的
代码,不然由于遍历方式的改变,致使不能顺利往页面中添加 div节点。数组

咱们有必要把遍历 data 的职责提取出来,这正是迭代器模式的意义,迭代器模式提供了一
种方法来访问聚合对象,而不用暴露这个对象的内部表示。网络

当把迭代聚合对象的职责单独封装在 each 函数中后,即便之后还要增长新的迭代方式,我
们只须要修改 each 函数便可,使其只负责迭代聚合对象的单一职责, appendDiv 函数不会受到牵连,代码以下:app

var each = function( obj, callback ) {
var value,
i = 0,
length = obj.length,

isArray = isArraylike( obj ); // isArraylike 函数未实现,能够用jQuery 中的功能函数实现。

if ( isArray ) { // 迭代类数组
for ( ; i < length; i++ ) {
callback.call( obj[ i ], i, obj[ i ] );
}
} else {
for ( i in obj ) { // 迭代 object 对象
callback.call( obj[ i ], i, obj[ i ] );
}
}
return obj;
};

在ES5中的数组的迭代方法直接提供的迭代器模式的功能函数forEach;函数

  1. 单例模式:

当在页面中建立一个惟一的登录浮框时,使用单利模式是比较好的方式。一下采用惰性单例实现一个页面的登录浮框。单元测试

var createLoginLayer =(function(){ 
 var  loginLyer;
 return function(){

if(!loginLyer){
div = document.createElement( 'div' );
div.innerHTML = '我是登陆浮窗';
div.style.display = 'none';
document.body.appendChild( div );
 } 
return loginLyer;
 }

})()

上面的单例模式有两个职责,一个是管理的单例的职责,一个是建立登录浮框的职责。
如今咱们把管理单例的职责和建立登陆浮窗的职责分别封装在两个方法里,使其迎合单一职责原则。这两个方法能够
独立变化而互不影响,当它们链接在一块儿的时候,就完成了建立惟一登陆浮窗的功能,下面的代
码显然是更好的作法:

var getSingle = function(fn){
 var resukt;
 return function(){
  return  result || (result= fn.apply(this,arguments));
}  
}

var createLoginLayer=function(){
var div = document.createElement( 'div' );
div.innerHTML = '我是登陆浮窗';
document.body.appendChild( div );
return div;
}
var createSingleLoginLayer = getSingle( createLoginLayer );

var loginLayer1 = createSingleLoginLayer();
var loginLayer2 = createSingleLoginLayer();
alert ( loginLayer1 === loginLayer2 ); // 输出: true

这是单一职责原则在单例模式中体现。

4. 装饰者模式

使用装饰者模式的时候,咱们一般让类或者对象一开始只具备一些基础的职责,更多的职责
在代码运行时被动态装饰到对象上面。装饰者模式能够为对象动态增长职责,从另外一个角度来看,
这也是分离职责的一种方式。

当用分离与核心业务逻辑模块代码无关的模块时,将其分离。(体现为面向切面编程,AOP)

当实现一个数据上报的功能时,数据上报通常一个登录浮层为载体。

<button tag="login" id="button">点击打开登陆浮层</button>

Function.prototype.after=function(fn){
var self= this;
return function () {
var ret;
 ret= self.apply(this,arguments); 
 fn.apply(this,arguments);
return ret;
}
}

var showLogin = function(){
console.log( '打开登陆浮层' );
};

var log = function(){
console.log( '上报标签为: ' + this.getAttribute( 'tag' ) );
};

document.getElementById( 'button' ).onclick = showLogin.after( log );
// 打开登陆浮层以后上报数据

SRP原则的应用难点就是如何去分离职责。

什么时候应该分离职责

SRP原则是全部原则中最简单也是最难正确运用的原则之一。

要明确的是,并非全部的职责都应该一一分离。
一方面,若是随着需求的变化,有两个职责老是同时变化,那就没必要分离他们。好比在 ajax
请求的时候,建立 xhr 对象和发送 xhr 请求几乎老是在一块儿的,那么建立 xhr 对象的职责和发送
xhr 请求的职责就没有必要分开。
另外一方面,职责的变化轴线仅当它们肯定会发生变化时才具备意义,即便两个职责已经被耦
合在一块儿,但它们尚未发生改变的征兆,那么也许没有必要主动分离它们,在代码须要重构的
时候再进行分离也不迟。

违反 SRP 原则

在人的常规思惟中,老是习惯性地把一组相关的行为放到一块儿,如何正确地分离职责不是一
件容易的事情。
咱们也许历来没有考虑过如何分离职责,但这并不妨碍咱们编写代码完成需求。对于 SRP
原则,许多专家委婉地表示“This is sometimes hard to see.”。
一方面,咱们受设计原则的指导,另外一方面,咱们未必要在任什么时候候都一成不变地遵照原则。
在实际开发中,由于种种缘由违反 SRP的状况并很多见。好比 jQuery的 attr 等方法,就是明显
违反 SRP 原则的作法。jQuery 的 attr 是个很是庞大的方法,既负责赋值,又负责取值,这对于
jQuery的维护者来讲,会带来一些困难,但对于 jQuery的用户来讲,却简化了用户的使用。
在方便性与稳定性之间要有一些取舍。具体是选择方便性仍是稳定性,并无标准答案,而
是要取决于具体的应用环境。好比若是一个电视机内置了 DVD机,当电视机坏了的时候,DVD
机也无法正常使用,那么一个 DVD 发烧友一般不会选择这样的电视机。但若是咱们的客厅原本
就小得夸张,或者更在乎 DVD 在使用上的方便,那让电视机和 DVD 机耦合在一块儿就是更好的
选择。

SRP 原则的优缺点

SRP 原则的优势是下降了单个类或者对象的复杂度,按照职责把对象分解成更小的粒度, 这有助于代码的复用,也有利于进行单元测试。当一个职责须要变动的时候,不会影响到其余 的职责。 但 SRP 原则也有一些缺点,最明显的是会增长编写代码的复杂度。当咱们按照职责把对象 分解成更小的粒度以后,实际上也增大了这些对象之间相互联系的难度。

相关文章
相关标签/搜索