何謂單一職責(zé)原則
按字面理解,單一職責(zé)原則就是自己只負(fù)責(zé)自己的事木人,不需要理會別人的事麸锉。如果了解面對對象編程钠绍,那么應(yīng)該會很容易了解這個單一職責(zé)原則。
在面對對象編程中淮椰,每個對象只負(fù)責(zé)自己的任務(wù)五慈,比如該提供數(shù)據(jù)的就只是提供數(shù)據(jù)纳寂,該負(fù)責(zé)提供服務(wù)的就只提供服務(wù)主穗,或者只是維護(hù)對象之間的關(guān)系,這樣的開發(fā)方式代碼耦合度較低毙芜,較靈活忽媒,易擴(kuò)展。當(dāng)然也可以一個對象負(fù)責(zé)多個任務(wù)腋粥,但是任務(wù)多了修改起來就比較容易影響到其他的任務(wù)晦雨。
從Object Design: Roles, Responsibilies, and Collaborations這本書中提出可以從以下幾方面判斷出一個對象的多個行為構(gòu)造出的是多職責(zé)還是單職責(zé)。
1隘冲、Information holder - 該對象設(shè)計為存儲對象并提供對象信息給其他對象
2闹瞧、Structurer - 該對象設(shè)計為維護(hù)對象與信息間的關(guān)系
3、Service provider - 該對象設(shè)計為處理任務(wù)與提供服務(wù)給其他對象
4展辞、Controller - 該對象設(shè)計為負(fù)責(zé)控制一系列職責(zé)的任務(wù)處理
5奥邮、Coordinator - 該對象設(shè)計為把任務(wù)綁定/委托到其他對象上
6、Interfacer - 該對象設(shè)計為在各個對象間負(fù)責(zé)轉(zhuǎn)化信息或者請求
一旦你知道了這些概念,那就狠容易知道你的代碼到底是多職責(zé)還是單一職責(zé)了洽腺。
實現(xiàn)物品添加購物車
有這樣的需求脚粟,有一些產(chǎn)品,需要以產(chǎn)品列表形式展示蘸朋,并且提供雙擊產(chǎn)品添加到購物車核无。有一個購物車,接收產(chǎn)品添加到購物車的操作藕坯,添加之后并顯示出來
這種簡單的需求如果使用面對過程的方式來實現(xiàn)時很容易的团南,代碼量也很少,但是不益于以后擴(kuò)展炼彪。比如資源的來源已慢、種類變了,或者添加方式變了霹购,改一個東西都容易影響到其他邏輯佑惠,使用面對對象的方式來實現(xiàn)就會比較靈活,可以把這個需求抽象齐疙,然后再慢慢實現(xiàn)膜楷。
簡單抽象,就可以抽象出產(chǎn)品贞奋、購物車兩個對象赌厅,購物車與產(chǎn)品需要通信所以需要一個通信的對象。再細(xì)化一下轿塔,把產(chǎn)品與購物車細(xì)分為信息提供與負(fù)責(zé)交互的兩個對象并把這些對象轉(zhuǎn)化為編程對象特愿。
由上可以抽象出6個對象,產(chǎn)品對象勾缭、事件對象揍障、購物車對象均有一個負(fù)責(zé)對外的對象,類似于門面模式俩由。Product毒嫡、Event、Cart這三個對象比較靈活幻梯,可以復(fù)用或者拓展兜畸,當(dāng)然實現(xiàn)起來會相對復(fù)雜一點,代碼量也會多一點碘梢。但是這只是相對于簡單不需要拓展的需求上咬摇,如果是比較龐大的需求或者是比較靈活的架構(gòu),使用面對對象編程的方式不僅可以節(jié)省代碼煞躬,而且擴(kuò)展維護(hù)更方便肛鹏,使用哪種方式只是根據(jù)需求來選擇了。
再對這些對象細(xì)化成基本工程,以下是基本交互思路圖龄坪。
以下是具體代碼實現(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é)對外交互
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 商品對象
*/
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;
事件對象Event.js,提供基本事件處理
function Event(name) {
this.handlers = []; // 事件回調(diào)數(shù)組
/**
* 獲取事件名稱
*
* @return {objecg} 事件對象
*/
this.getName = function() {
return name;
};
/**
* 給事件添加處理函數(shù)
*
*/
this.addHandler = function(handler) {
this.handlers.push(handler);
};
/**
* 刪除已經(jīng)添加的事件處理函數(shù)
*
* @param {function} handler 需要刪除的事件處理函數(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ā)布烛卧、訂閱方法給其他對象進(jìn)行通信
var Event = require('./Event.js');
function EventAggregator() {
this.events = []; // 事件對象集合
/**
* 根據(jù)事件名稱獲取事件對象
*
* @param {string} eventName 事件名稱
* @return {object} 事件對象
*/
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ù)
* @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ù)
* @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;
以上代碼就是購物車需求的實現(xiàn),代碼相對于面向過程變成的確挺多妓局,但是職責(zé)清晰总放,易于理解。以上代碼實現(xiàn)均參考湯姆大叔博客好爬。