單一職責原則(SRP)的職責被定義爲「引發變化的緣由」。若是咱們有兩個動機去改寫一 個方法,那麼這個方法就具備兩個職責。每一個職責都是變化的一個軸線,若是一個方法承擔了過 多的職責,那麼在需求的變遷過程當中,須要改寫這個方法的可能性就越大。 此時,這個方法一般是一個不穩定的方法,修改代碼老是一件危險的事情,特別是當兩個職 責耦合在一塊兒的時候,一個職責發生變化可能會影響到其餘職責的實現,形成意想不到的破壞, 這種耦合性獲得的是低內聚和脆弱的設計。 所以,SRP原則體現爲:一個對象(方法)只作一件事情。
設計模式中的 SRP 原則,SRP原則在不少設計模式中都有着普遍的運用,例如代理模式、迭代器模式、單例模式和裝飾者模式。javascript
好比如今實現一個圖片預加載的例子:你們知道當網速很慢的狀況下,把網速調至 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 標籤的功能和預加載圖片的職責分開放到兩個對象中,這兩個對象各自都只有一個被修改的動機。在它們各自發生改變的時候,也不會影響另外的對象。設計模式
咱們有這樣一段代碼,先遍歷一個集合,而後往頁面中添加一些 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;函數
當在頁面中建立一個惟一的登錄浮框時,使用單利模式是比較好的方式。一下采用惰性單例實現一個頁面的登錄浮框。單元測試
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
這是單一職責原則在單例模式中體現。
使用裝飾者模式的時候,咱們一般讓類或者對象一開始只具備一些基礎的職責,更多的職責
在代碼運行時被動態裝飾到對象上面。裝飾者模式能夠爲對象動態增長職責,從另外一個角度來看,
這也是分離職責的一種方式。
當用分離與核心業務邏輯模塊代碼無關的模塊時,將其分離。(體現爲面向切面編程,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
原則,許多專家委婉地表示「This is sometimes hard to see.」。
一方面,咱們受設計原則的指導,另外一方面,咱們未必要在任什麼時候候都一成不變地遵照原則。
在實際開發中,由於種種緣由違反 SRP的狀況並很多見。好比 jQuery的 attr 等方法,就是明顯
違反 SRP 原則的作法。jQuery 的 attr 是個很是龐大的方法,既負責賦值,又負責取值,這對於
jQuery的維護者來講,會帶來一些困難,但對於 jQuery的用戶來講,卻簡化了用戶的使用。
在方便性與穩定性之間要有一些取捨。具體是選擇方便性仍是穩定性,並無標準答案,而
是要取決於具體的應用環境。好比若是一個電視機內置了 DVD機,當電視機壞了的時候,DVD
機也無法正常使用,那麼一個 DVD 發燒友一般不會選擇這樣的電視機。但若是咱們的客廳原本
就小得誇張,或者更在乎 DVD 在使用上的方便,那讓電視機和 DVD 機耦合在一塊兒就是更好的
選擇。
SRP 原則的優勢是下降了單個類或者對象的複雜度,按照職責把對象分解成更小的粒度, 這有助於代碼的複用,也有利於進行單元測試。當一個職責須要變動的時候,不會影響到其餘 的職責。 但 SRP 原則也有一些缺點,最明顯的是會增長編寫代碼的複雜度。當咱們按照職責把對象 分解成更小的粒度以後,實際上也增大了這些對象之間相互聯繫的難度。