設計原則の単一職責原則

6643 ワード

1つのクラスでは、その変化の原因は1つしかないはずです.JavaScriptではクラスを使用する必要があるシーンはあまり多くなく、単一の職責の原則はオブジェクトやメソッドレベルに適用されることが多いため、本節ではオブジェクトとメソッドに基づいて議論することが多い.
単一職責原則(SRP)の職責は「変化の原因」と定義される.もし私たちが2つの動機で1つの方法を書き換えるならば、この方法は2つの職責を持っています.各職責は変化の軸線であり、1つの方法が過剰な職責を負っている場合、需要の変遷過程において、この方法を書き換える必要がある可能性が高い.
この場合、この方法は通常不安定な方法であり、コードを修正することは常に危険なことであり、特に2つの職責が結合されている場合、1つの職責が変化して他の職責の実現に影響を与え、予想外の破壊をもたらす可能性があり、この結合性は低集約と脆弱な設計を得る.
したがって,SRPの原則は,一つの対象(方法)が一つのことしかしないことを表す.

設計モードにおけるSRPの原則


SRPの原則は、エージェントモード、反復器モード、単例モード、装飾者モードなど、多くの設計モードで広く用いられている.

1.エージェントモード


この画像のプリロードの例を前に見たことがあります.仮想エージェントを増やすことで、プリロードされたピクチャのロールをエージェントオブジェクトに配置し、ボリュームはページに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' );

imgタグを追加する機能と、ピクチャをプリロードする役割を2つのオブジェクトに分けて配置します.この2つのオブジェクトには、それぞれ変更された動機が1つしかありません.それぞれが変化しても、他のオブジェクトには影響しません.

2.反復モード


まず1つの集合を巡り、ページに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 ] );

これは、ajaxリクエストの後、コールバック関数でajaxリクエストが返すデータを巡り、ページでノードをレンダリングする一般的なコードです.
appendDiv関数は、本来はデータのレンダリングを担当するだけですが、ここでは集約オブジェクトdataを巡る役割も果たしています.ある日cgiが返すdataデータフォーマットがarrayからobjectに変わると、dataを巡るコードに問題が発生し、for(var i in data)に変更しなければならない.このときappendDivのコードを変更しなければならない.そうしないと、遍歴方式の変更でdivノードをページにスムーズに追加できない.
我々はdataを巡る職責を抽出する必要がある.これは反復器モードの意味であり、反復器モードはこのオブジェクトの内部表現を暴露することなく、集約オブジェクトにアクセスする方法を提供する.
反復集約オブジェクトの役割をeach関数に単独でカプセル化すると、後で新しい反復方式を追加しても、each関数を変更するだけで、appendDiv関数は関連付けられません.コードは以下の通りです.
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  
              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} );

3.単例モード


以前は不活性な単一例が実装されていましたが、最初のコードは次のようになりました.
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;
    }
})();

次に、単一の例を管理する職責とログインフローティングウィンドウを作成する職責をそれぞれ2つの方法にカプセル化します.この2つの方法は独立して変化することができ、互いに影響を与えません.それらが接続されている場合、唯一のログインフローティングウィンドウを作成する機能が完了します.次のコードは明らかにより良い方法です.
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

4.デコレーションモード


デザイナモードを使用する場合、通常、クラスまたはオブジェクトに最初はいくつかの基礎的な役割しか持たせず、コードの実行時にオブジェクトに動的にデザイナされる役割が多くなります.装飾者モードは、オブジェクトに対して動的に職責を追加することができ、別の角度から見ると、これも職責を分離する方法の一つである.
次に、前に述べた例を示します.データ・レポートの機能を1つの関数に個別に配置し、この関数をビジネス関数に動的に装飾します.

    
        
    



Function.prototype.after = function( afterfn ){
    var __self = this;
    return function(){
        var ret = __self.apply( this, arguments );
        afterfn.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原則はすべての原則の中で最も簡単で最も正しく運用しにくい原則の一つである.
すべての職責が一つ一つ分離されるわけではないことを明確にしなければならない.
一方、需要の変化に伴って、2つの職責が常に同時に変化すれば、彼らを分離する必要はありません.たとえばajaxリクエストの場合、xhrオブジェクトの作成とxhrリクエストの送信はほぼ常に一緒であるため、xhrオブジェクトの作成の職責とxhrリクエストの送信の職責を分ける必要はありません.
一方、職責の変化軸線は、それらが変化すると判断した場合にのみ意味があり、2つの職責が結合されているにもかかわらず、それらがまだ変化の兆候が発生していない場合でも、それらをアクティブに分離する必要はなく、コードが再構築する必要がある場合に分離するのも遅くないかもしれない.

SRPの原則に違反する


人の通常の思考の中で、いつも習慣的に関連する行為を一緒に置いて、どのように職責を正しく分離するかは容易なことではありません.
職責をどのように分離するかは考えたことがないかもしれませんが、コードの作成のニーズを妨げることはありません.SRPの原則については、多くの専門家が「This is sometimes hard to see.」と婉曲に表現している.
一方、私たちは設計原則の指導を受けている一方で、いつでも原則を守っているとは限らない.実際の開発では,様々な理由でSRPに違反することは珍しくない.例えばjQueryのattrなどの方法は、SRPの原則に明らかに違反している.jQueryのattrは非常に膨大な方法であり、値の割り当てと値の取得を担当する.これはjQueryのメンテナンス者にとって、いくつかの困難をもたらすが、jQueryのユーザーにとって、ユーザーの使用を簡素化する.
利便性と安定性の間にはいくつかの取捨選択が必要です.具体的には、利便性と安定性のどちらを選択するかは、標準的な答えではなく、具体的なアプリケーション環境に依存します.例えば、1つのテレビにDVD機が内蔵されていて、テレビが壊れたとき、DVD機も正常に使用できない場合、1つのDVDマニアは通常このようなテレビを選択しません.しかし、私たちのリビングがもともと大げさに小さくて、DVDの使い勝手がもっと気になるなら、テレビとDVD機を結合させるのがもっと良い選択です.

SRP原則のメリットとデメリット


SRPの原則の利点は,単一クラスやオブジェクトの複雑さを低減し,職責に従ってオブジェクトをより小さな粒度に分解することであり,コードの多重化に寄与し,ユニットテストにも有利である.1つの職責を変更する必要がある場合、他の職責には影響しません.
しかしSRPの原則にもいくつかの欠点があり,最も明らかなのはコードの記述の複雑さを増加させることである.職責に従ってオブジェクトをより小さな粒度に分解すると,実際にはこれらのオブジェクト間の相互接続の難しさも増大する.