單一職責原則(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
複製程式碼
什麼時候使用SRP?
一方面,如果隨著需求的變化,有兩個職責總是同時變化,那就不必分離他們。比如在 ajax 請求的時候,建立 xhr 物件和傳送 xhr 請求幾乎總是在一起的,那麼建立 xhr 物件的職責和傳送 xhr 請求的職責就沒有必要分開。 另一方面,職責的變化軸線僅當它們確定會發生變化時才具有意義,即使兩個職責已經被耦 合在一起,但它們還沒有發生改變的徵兆,那麼也許沒有必要主動分離它們,在程式碼需要重構的 時候再進行分離也不遲。
總結
SRP 原則的優點是降低了單個類或者物件的複雜度,按照職責把物件分解成更小的粒度, 這有助於程式碼的複用,也有利於進行單元測試。當一個職責需要變更的時候,不會影響到其他的職責。 但SRP 原則也有一些缺點,最明顯的是會增加編寫程式碼的複雜度。當我們按照職責把物件分解成更小的粒度之後,實際上也增大了這些物件之間相互聯絡的難度。