何謂單一職責(zé)原則
按字面理解,單一職責(zé)原則就是自己只負(fù)責(zé)自己的事,不需要理會(huì)別人的事。如果了解面對(duì)對(duì)象編程,那么應(yīng)該會(huì)很容易了解這個(gè)單一職責(zé)原則。
在面對(duì)對(duì)象編程中,每個(gè)對(duì)象只負(fù)責(zé)自己的任務(wù),比如該提供數(shù)據(jù)的就只是提供數(shù)據(jù),該負(fù)責(zé)提供服務(wù)的就只提供服務(wù),或者只是維護(hù)對(duì)象之間的關(guān)系,這樣的開發(fā)方式代碼耦合度較低,較靈活,易擴(kuò)展。當(dāng)然也可以一個(gè)對(duì)象負(fù)責(zé)多個(gè)任務(wù),但是任務(wù)多了修改起來就比較容易影響到其他的任務(wù)。
從Object Design: Roles, Responsibilies, and Collaborations這本書中提出可以從以下幾方面判斷出一個(gè)對(duì)象的多個(gè)行為構(gòu)造出的是多職責(zé)還是單職責(zé)。
1、Information holder - 該對(duì)象設(shè)計(jì)為存儲(chǔ)對(duì)象并提供對(duì)象信息給其他對(duì)象
2、Structurer - 該對(duì)象設(shè)計(jì)為維護(hù)對(duì)象與信息間的關(guān)系
3、Service provider - 該對(duì)象設(shè)計(jì)為處理任務(wù)與提供服務(wù)給其他對(duì)象
4、Controller - 該對(duì)象設(shè)計(jì)為負(fù)責(zé)控制一系列職責(zé)的任務(wù)處理
5、Coordinator - 該對(duì)象設(shè)計(jì)為把任務(wù)綁定/委托到其他對(duì)象上
6、Interfacer - 該對(duì)象設(shè)計(jì)為在各個(gè)對(duì)象間負(fù)責(zé)轉(zhuǎn)化信息或者請求
一旦你知道了這些概念,那就狠容易知道你的代碼到底是多職責(zé)還是單一職責(zé)了。
實(shí)現(xiàn)物品添加購物車
有這樣的需求,有一些產(chǎn)品,需要以產(chǎn)品列表形式展示,并且提供雙擊產(chǎn)品添加到購物車。有一個(gè)購物車,接收產(chǎn)品添加到購物車的操作,添加之后并顯示出來
這種簡單的需求如果使用面對(duì)過程的方式來實(shí)現(xiàn)時(shí)很容易的,代碼量也很少,但是不益于以后擴(kuò)展。比如資源的來源、種類變了,或者添加方式變了,改一個(gè)東西都容易影響到其他邏輯,使用面對(duì)對(duì)象的方式來實(shí)現(xiàn)就會(huì)比較靈活,可以把這個(gè)需求抽象,然后再慢慢實(shí)現(xiàn)。

簡單抽象,就可以抽象出產(chǎn)品、購物車兩個(gè)對(duì)象,購物車與產(chǎn)品需要通信所以需要一個(gè)通信的對(duì)象。再細(xì)化一下,把產(chǎn)品與購物車細(xì)分為信息提供與負(fù)責(zé)交互的兩個(gè)對(duì)象并把這些對(duì)象轉(zhuǎn)化為編程對(duì)象。

由上可以抽象出6個(gè)對(duì)象,產(chǎn)品對(duì)象、事件對(duì)象、購物車對(duì)象均有一個(gè)負(fù)責(zé)對(duì)外的對(duì)象,類似于門面模式。Product、Event、Cart這三個(gè)對(duì)象比較靈活,可以復(fù)用或者拓展,當(dāng)然實(shí)現(xiàn)起來會(huì)相對(duì)復(fù)雜一點(diǎn),代碼量也會(huì)多一點(diǎn)。但是這只是相對(duì)于簡單不需要拓展的需求上,如果是比較龐大的需求或者是比較靈活的架構(gòu),使用面對(duì)對(duì)象編程的方式不僅可以節(jié)省代碼,而且擴(kuò)展維護(hù)更方便,使用哪種方式只是根據(jù)需求來選擇了。
再對(duì)這些對(duì)象細(xì)化成基本工程,以下是基本交互思路圖。

以下是具體代碼實(shí)現(xiàn)
Product.js 負(fù)責(zé)提供產(chǎn)品數(shù)據(jù)
function Product(id, description) {
/**
* 獲取商品ID
*
* @return {int } 商品id
*/
this.getId = function() {
return id;
};
/**
* 獲取商品描述
*
* @return {string} 商品描述
*/
this.getDescription = function() {
return description;
}
}
module.exports = Product;
產(chǎn)品控制器ProductController.js, 負(fù)責(zé)對(duì)外交互
var ProductRepository = require('./ProductRepository.js');
function ProductController(productRepository, eventAggregator) {
// 獲取所有的物品
var products = productRepository.getProducts();
this.onProductSelect = function(id) {
var product;
products.forEach(function(pro) {
if(pro.getId() == id){
product = pro;
}
console.log('product is ' + pro.getId());
});
// 觸發(fā)雙擊添加購物車事件
eventAggregator.publish('productSelected', {
product: product
});
}
// 列出所有物品
products.forEach(function(product) {
var id = product.getId(),
description = product.getDescription();
console.log('product ' + id + ' , and description is ' + description);
// 觸發(fā)雙擊添加購物車
if(id == 1){
this.onProductSelect(id);
}
}.bind(this));
}
module.exports = ProductController;
購物車基本功能
function Cart(eventAggregator) {
this.items = []; // 購物車列表
/**
* 添加物品到購物車
*
* @param {object} item 商品對(duì)象
*/
this.addItem = function(item) {
this.items.push(item);
console.log('add Item ' + item);
// 觸發(fā)添加購物車事件
eventAggregator.publish('itemAdded', item);
};
}
module.exports = Cart;
購物車控制器CartController.js ,負(fù)責(zé)購物車事件處理
function CartController(eventAggregator, cart) {
// 訂閱物品添加事件
eventAggregator.subscribe('itemAdded', function(eventArgs) {
var id = eventArgs.getId(),
description = eventArgs.getDescription();
console.log('the ' + id + 'has been add to cart &' + description);
});
// 訂閱物品加入購物車事件
eventAggregator.subscribe('productSelected', function(eventArgs) {
console.log('recieved productSelected event ' + eventArgs);
cart.addItem(eventArgs.product);
});
}
module.exports = CartController;
事件對(duì)象Event.js,提供基本事件處理
function Event(name) {
this.handlers = []; // 事件回調(diào)數(shù)組
/**
* 獲取事件名稱
*
* @return {objecg} 事件對(duì)象
*/
this.getName = function() {
return name;
};
/**
* 給事件添加處理函數(shù)
*
*/
this.addHandler = function(handler) {
this.handlers.push(handler);
};
/**
* 刪除已經(jīng)添加的事件處理函數(shù)
*
* @param {function} handler 需要?jiǎng)h除的事件處理函數(shù)
* @return {void}
*/
this.removeHandler = function(handler) {
var i = 0,
len = this.handlers.length;
for (; i < len; i++) {
if(this.handlers[i] == handler){
this.handlers.splice(i, 1);
break;
}
}
};
/**
* 執(zhí)行事件處理函數(shù)
*
* @param {*} eventArgs 處理函數(shù)調(diào)用參數(shù)
* @return {void}
*/
this.fire = function(eventArgs) {
this.handlers.forEach(function(h) {
h(eventArgs);
});
}
}
module.exports = Event;
事件聚合器EventAggregator.js,負(fù)責(zé)提供發(fā)布、訂閱方法給其他對(duì)象進(jìn)行通信
var Event = require('./Event.js');
function EventAggregator() {
this.events = []; // 事件對(duì)象集合
/**
* 根據(jù)事件名稱獲取事件對(duì)象
*
* @param {string} eventName 事件名稱
* @return {object} 事件對(duì)象
*/
function getEvent(eventName) {
var event;
this.events.forEach(function(ev) {
if (ev.getName() == eventName) {
event = ev;
}
});
//console.log('get event after ' + event);
return event;
}
/**
* 事件發(fā)布
*
* @param {string} eventName 事件名稱
* @param {* } eventArgs 調(diào)用事件處理函數(shù)時(shí)傳的參數(shù)
* @return {void}
*/
this.publish = function(eventName, eventArgs) {
var event = getEvent.call(this, eventName);
if (!event) {
event = new Event(eventName);
this.events.push(event);
}
event.fire(eventArgs);
};
/**
* 訂閱事件
*
* @param {string} eventName 事件名稱
* @param {function} handler 事件觸發(fā)時(shí)處理函數(shù)
* @return {void}
*/
this.subscribe = function(eventName, handler) {
var event = getEvent.call(this, eventName);
if (!event) {
event = new Event(eventName);
this.events.push(event);
console.log('add event ' + event.getName());
}
event.addHandler(handler);
}
}
module.exports = EventAggregator;
以上代碼就是購物車需求的實(shí)現(xiàn),代碼相對(duì)于面向過程變成的確挺多,但是職責(zé)清晰,易于理解。以上代碼實(shí)現(xiàn)均參考湯姆大叔博客。