JavaScriptシリーズ(6):S.O.L.I.Dの5つの原則を深く理解する単一の職責SRP

12127 ワード

前言
BobおじさんはS.O.L.I.Dの5つの原則を提出し、発揚し、対象向けのプログラミングをよりよく行うために使用され、5つの原則はそれぞれ:
The Single Responsibility Principle(単一職責SRP)
The Open/Closed Principle(開閉原則OCP)
The Liskov Substitution Principle(リッツ置換原則LSP)
The Interface Segregation Principle(インタフェース分離の原則ISP)
The Dependency Inversion Principle(依存反転原則DIP)
5つの原則は、ブログ園で議論されて腐っていると信じています.特にC#の実現ですが、JavaScriptという原型をbaseとするダイナミックタイプ言語に比べて数少ないです.このシリーズでは、5つの文章に分けてJavaScriptプログラミング言語をベースに5つの原則の応用を示します.OK、私たちの最初の編を始めます:単一の職責.
英文原文:http://freshbrewedcode.com/derekgreer/2011/12/08/solid-javascript-single-responsibility-principle/
単一の職責
単一の職責の説明は以下の通りです.
A class should have only one reason to change
クラスが変更された理由は1つしかないはずです.
クラス(JavaScriptの下ではオブジェクト)に密接に関連する動作のセットがあるべきです.単一の職責を遵守する利点は、このオブジェクトを容易に維持できることです.1つのオブジェクトに多くの職責がカプセル化されている場合、1つの職責が変更されると、そのオブジェクトが考えている他の職責コードに影響を与えることになります.デカップリングにより、各職責労働者をより弾力的に変化させることができる.
しかし、1つのオブジェクトの複数の動作が複数のロールを構築するか、単一のロールを構築するかをどのように知るか.私たちはObject Design:Roles,Responsibilies,and Collaborationsの本が提出したRole Stereotypesの概念を参照して決定することができ、この本は次のようなRole Stereotypesを提出して職責を区別することができます.
Information holderは、オブジェクトを格納し、他のオブジェクトにオブジェクト情報を提供するように設計されています.
Structurer�Cこのオブジェクトは、オブジェクトと情報の関係を維持するように設計されています.
サービスプロバイダCこのオブジェクトは、作業を処理し、他のオブジェクトにサービスを提供するように設計されています.
Controller�Cこのオブジェクトは、一連の責任あるタスク処理の決定を制御するように設計されている
Coordinator�Cこのオブジェクトはいかなる決定処理の仕事をしないで、ただdelegateは他のオブジェクトの上で仕事をします
Interfacer�Cこのオブジェクトはシステムの各部分で情報(または要求)を変換するように設計されている.
これらの概念を知ったら、あなたのコードがどれだけの職責なのか、単一の職責なのかを簡単に知ることができます.
インスタンスコード
このインスタンスコードは、ショッピングカートに商品を追加することを示しています.コードは非常に悪いです.コードは以下の通りです.
 
  
function Product(id, description) {
this.getId = function () {
return id;
};
this.getDescription = function () {
return description;
};
}

function Cart(eventAggregator) {
var items = [];

this.addItem = function (item) {
items.push(item);
};
}

(function () {
var products = [new Product(1, "Star Wars Lego Ship"),
new Product(2, "Barbie Doll"),
new Product(3, "Remote Control Airplane")],
cart = new Cart();

function addToCart() {
var productId = $(this).attr('id');
var product = $.grep(products, function (x) {
return x.getId() == productId;
})[0];
cart.addItem(product);

var newItem = $('
  • ').html(product.getDescription()).attr('id-cart', product.getId()).appendTo("#cart");
    }

    products.forEach(function (product) {
    var newItem = $('
  • ').html(product.getDescription())
    .attr('id', product.getId())
    .dblclick(addToCart)
    .appendTo("#products");
    });
    })();

  • 该代码声明了2个function分别用来描述product和cart,而匿名函数的职责是更新屏幕和用户交互,这还不是一个很复杂的例子,但匿名函数里却包含了很多不相关的职责,让我们来看看到底有多少职责:

    首先,有product的集合的声明
    其次,有一个将product集合绑定到#product元素的代码,而且还附件了一个添加到购物车的事件处理
    第三,有Cart购物车的展示功能
    第四,有添加product item到购物车并显示的功能
    重构代码
    让我们来分解一下,以便代码各自存放到各自的对象里,为此,我们参考了martinfowler的事件聚合(Event Aggregator)理论在处理代码以便各对象之间进行通信。

    首先我们先来实现事件聚合的功能,该功能分为2部分,1个是Event,用于Handler回调的代码,1个是EventAggregator用来订阅和发布Event,代码如下:
     
      
    function Event(name) {
    var handlers = [];

    this.getName = function () {
    return name;
    };

    this.addHandler = function (handler) {
    handlers.push(handler);
    };

    this.removeHandler = function (handler) {
    for (var i = 0; i < handlers.length; i++) {
    if (handlers[i] == handler) {
    handlers.splice(i, 1);
    break;
    }
    }
    };

    this.fire = function (eventArgs) {
    handlers.forEach(function (h) {
    h(eventArgs);
    });
    };
    }

    function EventAggregator() {
    var events = [];

    function getEvent(eventName) {
    return $.grep(events, function (event) {
    return event.getName() === eventName;
    })[0];
    }

    this.publish = function (eventName, eventArgs) {
    var event = getEvent(eventName);

    if (!event) {
    event = new Event(eventName);
    events.push(event);
    }
    event.fire(eventArgs);
    };

    this.subscribe = function (eventName, handler) {
    var event = getEvent(eventName);

    if (!event) {
    event = new Event(eventName);
    events.push(event);
    }

    event.addHandler(handler);
    };
    }

    次に、Productオブジェクトを宣言します.コードは次のとおりです.
     
      
    function Product(id, description) {
    this.getId = function () {
    return id;
    };
    this.getDescription = function () {
    return description;
    };
    }

    次にCartオブジェクトを宣言します.このオブジェクトのaddItemのfunctionでは、イベントitemAddedの発行をトリガーし、itemをパラメータとして送信します.
     
      
    function Cart(eventAggregator) {
    var items = [];

    this.addItem = function (item) {
    items.push(item);
    eventAggregator.publish("itemAdded", item);
    };
    }

    CartControllerは主にcartオブジェクトとイベント集約器を受け入れ、itemAddedを購読することでli要素ノードを追加し、productSelectedイベントを購読することでproductを追加します.
     
      
    function CartController(cart, eventAggregator) {
    eventAggregator.subscribe("itemAdded", function (eventArgs) {
    var newItem = $('
  • ').html(eventArgs.getDescription()).attr('id-cart', eventArgs.getId()).appendTo("#cart");
    });

    eventAggregator.subscribe("productSelected", function (eventArgs) {
    cart.addItem(eventArgs.product);
    });
    }

  • Repository的目的是为了获取数据(可以从ajax里获取),然后暴露get数据的方法。
     
      
    function ProductRepository() {
    var products = [new Product(1, "Star Wars Lego Ship"),
    new Product(2, "Barbie Doll"),
    new Product(3, "Remote Control Airplane")];

    this.getProducts = function () {
    return products;
    }
    }

    ProductControllerでは、productSelectedイベントをトリガーするonProductSelectctメソッドが定義されています.forEachは主に製品リストにデータをバインドするために使用されます.コードは次のとおりです.
     
      
    function ProductController(eventAggregator, productRepository) {
    var products = productRepository.getProducts();

    function onProductSelected() {
    var productId = $(this).attr('id');
    var product = $.grep(products, function (x) {
    return x.getId() == productId;
    })[0];
    eventAggregator.publish("productSelected", {
    product: product
    });
    }

    products.forEach(function (product) {
    var newItem = $('
  • ').html(product.getDescription())
    .attr('id', product.getId())
    .dblclick(onProductSelected)
    .appendTo("#products");
    });
    }

  • 最后声明匿名函数:
     
      
    (function () {
    var eventAggregator = new EventAggregator(),
    cart = new Cart(eventAggregator),
    cartController = new CartController(cart, eventAggregator),
    productRepository = new ProductRepository(),
    productController = new ProductController(eventAggregator, productRepository);
    })();

    匿名関数のコードが大幅に減少していることがわかります.主にオブジェクトのインスタンス化コードです.コードにはControllerの概念を紹介しています.彼は情報を受け取ってactionに伝えました.Repositoryの概念も紹介しました.主にproductの展示を処理するために使われています.再構築の結果、オブジェクト宣言がたくさん書かれています.しかし、利点は、各オブジェクトに明確な職責があり、この展示データの展示データは、集合の処理集合を処理し、結合度が非常に低いことである.
    最終コード
    function Event(name) {
    var handlers = [];

    this.getName = function () {
    return name;
    };

    this.addHandler = function (handler) {
    handlers.push(handler);
    };

    this.removeHandler = function (handler) {
    for (var i = 0; i < handlers.length; i++) {
    if (handlers[i] == handler) {
    handlers.splice(i, 1);
    break;
    }
    }
    };

    this.fire = function (eventArgs) {
    handlers.forEach(function (h) {
    h(eventArgs);
    });
    };
    }

    function EventAggregator() {
    var events = [];

    function getEvent(eventName) {
    return $.grep(events, function (event) {
    return event.getName() === eventName;
    })[0];
    }

    this.publish = function (eventName, eventArgs) {
    var event = getEvent(eventName);

    if (!event) {
    event = new Event(eventName);
    events.push(event);
    }
    event.fire(eventArgs);
    };

    this.subscribe = function (eventName, handler) {
    var event = getEvent(eventName);

    if (!event) {
    event = new Event(eventName);
    events.push(event);
    }

    event.addHandler(handler);
    };
    }

    function Product(id, description) {
    this.getId = function () {
    return id;
    };
    this.getDescription = function () {
    return description;
    };
    }

    function Cart(eventAggregator) {
    var items = [];

    this.addItem = function (item) {
    items.push(item);
    eventAggregator.publish("itemAdded", item);
    };
    }

    function CartController(cart, eventAggregator) {
    eventAggregator.subscribe("itemAdded", function (eventArgs) {
    var newItem = $('
  • ').html(eventArgs.getDescription()).attr('id-cart', eventArgs.getId()).appendTo("#cart");
    });

    eventAggregator.subscribe("productSelected", function (eventArgs) {
    cart.addItem(eventArgs.product);
    });
    }

    function ProductRepository() {
    var products = [new Product(1, "Star Wars Lego Ship"),
    new Product(2, "Barbie Doll"),
    new Product(3, "Remote Control Airplane")];

    this.getProducts = function () {
    return products;
    }
    }

    function ProductController(eventAggregator, productRepository) {
    var products = productRepository.getProducts();

    function onProductSelected() {
    var productId = $(this).attr('id');
    var product = $.grep(products, function (x) {
    return x.getId() == productId;
    })[0];
    eventAggregator.publish("productSelected", {
    product: product
    });
    }

    products.forEach(function (product) {
    var newItem = $('
  • ').html(product.getDescription())
    .attr('id', product.getId())
    .dblclick(onProductSelected)
    .appendTo("#products");
    });
    }

    (function () {
    var eventAggregator = new EventAggregator(),
    cart = new Cart(eventAggregator),
    cartController = new CartController(cart, eventAggregator),
    productRepository = new ProductRepository(),
    productController = new ProductController(eventAggregator, productRepository);
    })();

  • まとめ
    この再構築の結果を見て、ブロガーが聞くかもしれませんが、本当にこんなに複雑にする必要がありますか?そうするかどうかはあなたのプロジェクトの状況にかかっているとしか言えません.
    もしあなたのプロジェクトが非常に小さなプロジェクトであれば、コードも多くありません.それは実際にはそんなに複雑に再構築する必要はありませんが、もしあなたのプロジェクトが複雑な大型プロジェクトであれば、あるいはあなたの小さなプロジェクトが将来急速に成長する可能性があるならば、前期にSRPの原則を考慮して職責を分離しなければなりません.そうすれば、今後のメンテナンスに役立ちます.