设计原则之单一职责原则

参考资料

  • 曾探《JavaScript设计模式与开发实践》;
  • 《JavaScript设计模式与开发实践》原则篇(1)—— 单一职责原则
  • 《javaScript设计模式与开发实践》笔记

定义

单一职责原则(Single Responsibility Principle SRP)的职责:一个对象(方法)只做一件事情。

单一职责原则的职责被定义为“引起变化的原因”。如果我们有两个动机去改写一个方法,那么这个方法就具有两个职责。每个职责都是变化的一个轴线,如果一个方法承担了过多的职责,那么在需求的变迁过程中,需要改写这个方法的可能性就越大。

此时,这个方法通常是一个不稳定的方法,修改代码总是一件危险的事情,特别是当两个职 责耦合在一起的时候,一个职责发生变化可能会影响到其他职责的实现,造成意想不到的破坏, 这种耦合性得到的是低内聚和脆弱的设计。 因此,SRP 原则体现为:一个对象(方法)只做一件事情。

使用场景:

  • 代理模式;
  • 迭代器模式;
  • 单例模式;
  • 装饰者模式;

设计模式中的SRP原则

  • 代理模式
    图片预加载:通过增加虚拟代理的方式,把预加载图片的职责放到代理对象中,而本体仅仅负责往页面中添加 img 标签,这也是它最原始的职责 把添加 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/svenzeng/Desktop/loading.gif' );
            img.src = src; 
        }
     } 
})();
proxyImage.setSrc('http://imgcache.qq.com/music/photo/000GGDys0yA0Nk.jpg' );
  • 迭代器模式 我们有这样一段代码,先遍历一个集合,然后往页面中添加一些 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 的职责

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

var each = function( obj, callback ) { 
    var value,
    i = 0,
    length = obj.length,
    isArray = isArraylike( obj );
    if ( isArray ) { // 迭代类数组 
        for ( ; i < length; i++ ) {// isArraylike 函数未实现,可以翻阅 jQuery 源代码
            callback.call( obj[ i ], i, obj[ i ] ); 
        }
    } else {
        for ( i in obj ) { // 迭代object对象
            value = callback.call( obj[ i ], i, obj[ i ] ); 
        }
    }
    return obj;
};
var appendDiv = function( data ){ 
    each( data, function( i, n ){
        var div = document.createElement( 'div' ); 
        div.innerHTML = n; document.body.appendChild( div );
    }); 
};
appendDiv( [ 1, 2, 3, 4, 5, 6 ] ); 
appendDiv({a:1,b:2,c:3,d:4} );
  • 单例模式 最开始的登录窗惰性单例
var createLoginLayer = (function(){ 
    var div;
    return function(){ 
        if ( !div ){
            div = document.createElement( 'div' ); 
            div.innerHTML = '我是登录浮窗'; 
            div.style.display = 'none'; 
            document.body.appendChild( div );
        }
        return div; 
    }
})();

现在我们把管理单例的职责和创建登录浮窗的职责分别封装在两个方法里,这两个方法可以 独立变化而互不影响,当它们连接在一起的时候,就完成了创建唯一登录浮窗的功能

var getSingle = function( fn ){ // 获取单例 var result;
    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

何时应该分离职责

一方面,如果随着需求的变化,有两个职责总是同时变化,那就不必分离他们。比如在 ajax 请求的时候,创建 xhr 对象和发送 xhr 请求几乎总是在一起的,那么创建 xhr 对象的职责和发送 xhr 请求的职责就没有必要分开。

另一方面,职责的变化轴线仅当它们确定会发生变化时才具有意义,即使两个职责已经被耦合在一起,但它们还没有发生改变的征兆,那么也许没有必要主动分离它们,在代码需要重构的时候再进行分离也不迟

应用建议:方便性与稳定性取舍问题,根据具体情况判定,不一定非得遵守该原则;什么情况下不需要分离?

  • 随需求变化,两个职责都同时变化,就不需要分离,比如xhr创建请求对象和发送xhr对象
  • 两个职责耦合在一起但没有变化的征兆

SRP原则的优缺点

  • 优点

    SRP 原则的优点是降低了单个类或者对象的复杂度,按照职责把对象分解成更小的粒度, 这有助于代码的复用,也有利于进行单元测试。当一个职责需要变更的时候,不会影响到其他的职责。

  • 缺点

    最明显的是会增加编写代码的复杂度。当我们按照职责把对象分解成更小的粒度之后,实际上也增大了这些对象之间相互联系的难度。

你可能感兴趣的:(设计模式,单一职责原则,javascript,设计模式)