單一職責(zé)原則的理解與實(shí)現(xiàn)

何謂單一職責(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)。

Paste_Image.png

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

Paste_Image.png

由上可以抽象出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ì)化成基本工程,以下是基本交互思路圖。

Paste_Image.png

以下是具體代碼實(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)均參考湯姆大叔博客。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1.埋點(diǎn)是做什么的 2.如何進(jìn)行埋點(diǎn) 3.埋點(diǎn)方案的設(shè)計(jì) 近期常被問到這個(gè)問題,我擔(dān)心我的答案會(huì)將一些天真爛漫的孩...
    lxg閱讀 2,365評(píng)論 0 1
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,323評(píng)論 25 708
  • 用兩張圖告訴你,為什么你的 App 會(huì)卡頓? - Android - 掘金 Cover 有什么料? 從這篇文章中你...
    hw1212閱讀 14,104評(píng)論 2 59
  • 如果你問我對(duì)一個(gè)人最高的評(píng)價(jià)是什么? “有趣?!?有人說一個(gè)人有趣是因?yàn)橛袗酆捅粣郏挥腥苏f有趣是因?yàn)榻?jīng)歷得多、見得...
    captain_嫣閱讀 4,905評(píng)論 54 140
  • 昨天早上爸爸跑到格格床上來看她,丫頭睜開眼對(duì)著爸爸說:“你終于回來了!”因?yàn)榘职肿蛲砗芡聿呕貋?,她都已?jīng)睡著了。孩...
    格格麻麻閱讀 270評(píng)論 0 0

友情鏈接更多精彩內(nèi)容