在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ HTML/ S.O.L.I.D 五大原則之單一職責(zé) SRP
代碼復(fù)用模式(避免篇)
S.O.L.I.D 五大原則之接口隔離原則 ISP
設(shè)計(jì)模式之狀態(tài)模式
JavaScript 核心(晉級(jí)高手必讀篇)
設(shè)計(jì)模式之建造者模式
JavaScript 與 DOM(上)——也適用于新手
設(shè)計(jì)模式之中介者模式
設(shè)計(jì)模式之裝飾者模式
設(shè)計(jì)模式之模板方法
設(shè)計(jì)模式之外觀模式
強(qiáng)大的原型和原型鏈
設(shè)計(jì)模式之構(gòu)造函數(shù)模式
揭秘命名函數(shù)表達(dá)式
深入理解J avaScript 系列(結(jié)局篇)
執(zhí)行上下文(Execution Contexts)
函數(shù)(Functions)
《你真懂 JavaScript 嗎?》答案詳解
設(shè)計(jì)模式之適配器模式
設(shè)計(jì)模式之組合模式
設(shè)計(jì)模式之命令模式
S.O.L.I.D 五大原則之單一職責(zé) SRP
編寫高質(zhì)量 JavaScript 代碼的基本要點(diǎn)
求值策略
閉包(Closures)
對(duì)象創(chuàng)建模式(上篇)
This? Yes,this!
設(shè)計(jì)模式之代理模式
變量對(duì)象(Variable Object)
S.O.L.I.D 五大原則之里氏替換原則 LSP
面向?qū)ο缶幊讨话憷碚?/span>
設(shè)計(jì)模式之單例模式
Function 模式(上篇)
S.O.L.I.D 五大原則之依賴倒置原則 DIP
設(shè)計(jì)模式之迭代器模式
立即調(diào)用的函數(shù)表達(dá)式
設(shè)計(jì)模式之享元模式
設(shè)計(jì)模式之原型模式
根本沒有“JSON 對(duì)象”這回事!
JavaScript 與 DOM(下)
面向?qū)ο缶幊讨?ECMAScript 實(shí)現(xiàn)
全面解析 Module 模式
對(duì)象創(chuàng)建模式(下篇)
設(shè)計(jì)模式之職責(zé)鏈模式
S.O.L.I.D 五大原則之開閉原則 OCP
設(shè)計(jì)模式之橋接模式
設(shè)計(jì)模式之策略模式
設(shè)計(jì)模式之觀察者模式
代碼復(fù)用模式(推薦篇)
作用域鏈(Scope Chain)
Function 模式(下篇)
設(shè)計(jì)模式之工廠模式

S.O.L.I.D 五大原則之單一職責(zé) SRP

前言

Bob 大叔提出并發(fā)揚(yáng)了 S.O.L.I.D 五大原則,用來(lái)更好地進(jìn)行面向?qū)ο缶幊蹋宕笤瓌t分別是:

  1. The Single Responsibility Principle(單一職責(zé) SRP)
  2. The Open/Closed Principle(開閉原則 OCP)
  3. The Liskov Substitution Principle(里氏替換原則 LSP)
  4. The Interface Segregation Principle(接口分離原則 ISP)
  5. The Dependency Inversion Principle(依賴反轉(zhuǎn)原則 DIP)

五大原則,我相信在博客園已經(jīng)被討論爛了,尤其是 C# 的實(shí)現(xiàn),但是相對(duì)于 JavaScript 這種以原型為 base 的動(dòng)態(tài)類型語(yǔ)言來(lái)說(shuō)還為數(shù)不多,該系列將分 5 篇文章以 JavaScript 編程語(yǔ)言為基礎(chǔ)來(lái)展示五大原則的應(yīng)用。 OK,開始我們的第一篇:?jiǎn)我宦氊?zé)。

單一職責(zé)

單一職責(zé)的描述如下:

    A class should have only one reason to change
    類發(fā)生更改的原因應(yīng)該只有一個(gè)

一個(gè)類(JavaScript 下應(yīng)該是一個(gè)對(duì)象)應(yīng)該有一組緊密相關(guān)的行為的意思是什么?遵守單一職責(zé)的好處是可以讓我們很容易地來(lái)維護(hù)這個(gè)對(duì)象,當(dāng)一個(gè)對(duì)象封裝了很多職責(zé)的話,一旦一個(gè)職責(zé)需要修改,勢(shì)必會(huì)影響該對(duì)象想的其它職責(zé)代碼。通過(guò)解耦可以讓每個(gè)職責(zé)工更加有彈性地變化。

不過(guò),我們?nèi)绾沃酪粋€(gè)對(duì)象的多個(gè)行為構(gòu)造多個(gè)職責(zé)還是單個(gè)職責(zé)?我們可以通過(guò)參考Object Design: Roles, Responsibilies, and Collaborations一書提出的 Role Stereotypes 概念來(lái)決定,該書提出了如下 Role Stereotypes 來(lái)區(qū)分職責(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ù)給其它對(duì)象
  4. Controller – 該對(duì)象設(shè)計(jì)為控制決策一系列負(fù)責(zé)的任務(wù)處理
  5. Coordinator – 該對(duì)象不做任何決策處理工作,只是delegate工作到其它對(duì)象上
  6. Interfacer – 該對(duì)象設(shè)計(jì)為在系統(tǒng)的各個(gè)部分轉(zhuǎn)化信息(或請(qǐng)求)

一旦你知道了這些概念,那就狠容易知道你的代碼到底是多職責(zé)還是單一職責(zé)了。

實(shí)例代碼

該實(shí)例代碼演示的是將商品添加到購(gòu)物車,代碼非常糟糕,代碼如下:

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 = $('<li></li>').html(product.getDescription()).attr('id-cart', product.getId()).appendTo("#cart");
    }
    products.forEach(function (product) {
        var newItem = $('<li></li>').html(product.getDescription())
                                    .attr('id', product.getId())
                                    .dblclick(addToCart)
                                    .appendTo("#products");
    });
})();

該代碼聲明了 2 個(gè) function 分別用來(lái)描述 product 和 cart,而匿名函數(shù)的職責(zé)是更新屏幕和用戶交互,這還不是一個(gè)很復(fù)雜的例子,但匿名函數(shù)里卻包含了很多不相關(guān)的職責(zé),讓我們來(lái)看看到底有多少職責(zé):

  1. 首先,有 product 的集合的聲明
  2. 其次,有一個(gè)將 product 集合綁定到 #product 元素的代碼,而且還附件了一個(gè)添加到購(gòu)物車的事件處理
  3. 第三,有 Cart 購(gòu)物車的展示功能
  4. 第四,有添加 product item 到購(gòu)物車并顯示的功能

重構(gòu)代碼

讓我們來(lái)分解一下,以便代碼各自存放到各自的對(duì)象里,為此,我們參考了 martinfowler 的事件聚合(Event Aggregator)理論在處理代碼以便各對(duì)象之間進(jìn)行通信。

首先我們先來(lái)實(shí)現(xiàn)事件聚合的功能,該功能分為 2 部分,1 個(gè)是 Event,用于 Handler 回調(diào)的代碼,1 個(gè)是 EventAggregator 用來(lái)訂閱和發(fā)布 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);
            };
        }

然后,我們來(lái)聲明 Product 對(duì)象,代碼如下:

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

接著來(lái)聲明 Cart 對(duì)象,該對(duì)象的 addItem 的 function 里我們要觸發(fā)發(fā)布一個(gè)事件 itemAdded,然后將 item 作為參數(shù)傳進(jìn)去。

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

CartController 主要是接受 cart 對(duì)象和事件聚合器,通過(guò)訂閱 itemAdded 來(lái)增加一個(gè) li 元素節(jié)點(diǎn),通過(guò)訂閱 productSelected 事件來(lái)添加 product。

function CartController(cart, eventAggregator) {
    eventAggregator.subscribe("itemAdded", function (eventArgs) {
        var newItem = $('<li></li>').html(eventArgs.getDescription()).attr('id-cart', eventArgs.getId()).appendTo("#cart");
    });
    eventAggregator.subscribe("productSelected", function (eventArgs) {
        cart.addItem(eventArgs.product);
    });
}

Repository 的目的是為了獲取數(shù)據(jù)(可以從 ajax 里獲?。缓蟊┞?get 數(shù)據(jù)的方法。

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 里定義了一個(gè) onProductSelect 方法,主要是發(fā)布觸發(fā) productSelected 事件,forEach 主要是用于綁定數(shù)據(jù)到產(chǎn)品列表上,代碼如下:

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 = $('<li></li>').html(product.getDescription())
                                    .attr('id', product.getId())
                                    .dblclick(onProductSelected)
                                    .appendTo("#products");
    });
}

最后聲明匿名函數(shù)(需要確保 HTML 都加載完了才能執(zhí)行這段代碼,比如放在 jQuery 的 ready 方法里):

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

可以看到匿名函數(shù)的代碼減少了很多,主要是一個(gè)對(duì)象的實(shí)例化代碼,代碼里我們介紹了 Controller 的概念,他接受信息然后傳遞到 action,我們也介紹了 Repository 的概念,主要是用來(lái)處理 product 的展示,重構(gòu)的結(jié)果就是寫了一大堆的對(duì)象聲明,但是好處是每個(gè)對(duì)象有了自己明確的職責(zé),該展示數(shù)據(jù)的展示數(shù)據(jù),改處理集合的處理集合,這樣耦合度就非常低了。

最終代碼

        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 = $('<li></li>').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 = $('<li></li>').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);
        })();

總結(jié)

如果你的項(xiàng)目是個(gè)是個(gè)非常小的項(xiàng)目,代碼也不是很多,那其實(shí)是沒有必要重構(gòu)得這么復(fù)雜,但如果你的項(xiàng)目是個(gè)很復(fù)雜的大型項(xiàng)目,或者你的小項(xiàng)目將來(lái)可能增長(zhǎng)得很快的話,那就在前期就得考慮 SRP 原則進(jìn)行職責(zé)分離了,這樣才有利于以后的維護(hù)。