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

S.O.L.I.D 五大原則之接口隔離原則 ISP

前言

本章我們要講解的是 S.O.L.I.D 五大原則 JavaScript 語言實現(xiàn)的第4篇,接口隔離原則 ISP(The Interface Segregation Principle)。

接口隔離原則的描述是:

Clients should not be forced to depend on methods they do not use.
不應(yīng)該強迫客戶依賴于它們不用的方法。

當(dāng)用戶依賴的接口方法即便只被別的用戶使用而自己不用,那它也得實現(xiàn)這些接口,換而言之,一個用戶依賴了未使用但被其他用戶使用的接口,當(dāng)其他用戶修改該接口時,依賴該接口的所有用戶都將受到影響。這顯然違反了開閉原則,也不是我們所期望的。

接口隔離原則 ISP 和單一職責(zé)有點類似,都是用于聚集功能職責(zé)的,實際上 ISP 可以被理解才具有單一職責(zé)的程序轉(zhuǎn)化到一個具有公共接口的對象。

JavaScript 接口

JavaScript 下我們改如何遵守這個原則呢?畢竟 JavaScript 沒有接口的特性,如果接口就是我們所想的通過某種語言提供的抽象類型來建立 contract 和解耦的話,那可以說還行,不過 JavaScript 有另外一種形式的接口。在 Design Patterns – Elements of Reusable Object-Oriented Software 一書中我們找到了接口的定義:

一個對象聲明的任意一個操作都包含一個操作名稱,參數(shù)對象和操作的返回值。我們稱之為操作符的簽名(signature)。
一個對象里聲明的所有的操作被稱為這個對象的接口(interface)。一個對象的接口描繪了所有發(fā)生在這個對象上的請求信息。

不管一種語言是否提供一個單獨的構(gòu)造來表示接口,所有的對象都有一個由該對象所有屬性和方法組成的隱式接口。參考如下代碼:

var exampleBinder = {};
exampleBinder.modelObserver = (function() {
    /* 私有變量 */
    return {
        observe: function(model) {
            /* 代碼 */
            return newModel;
        },
        onChange: function(callback) {
            /* 代碼 */
        }
    }
})();
exampleBinder.viewAdaptor = (function() {
    /* 私有變量 */
    return {
        bind: function(model) {
            /* 代碼 */
        }
    }
})();
exampleBinder.bind = function(model) {
    /* 私有變量 */
    exampleBinder.modelObserver.onChange(/* 回調(diào)callback */);
    var om = exampleBinder.modelObserver.observe(model);
    exampleBinder.viewAdaptor.bind(om);
    return om;
};

上面的 exampleBinder 類庫實現(xiàn)的功能是雙向綁定。該類庫暴露的公共接口是 bind 方法,其中 bind 里用到的關(guān)于 change 通知和 view 交互的功能分別是由單獨的對象 modelObserver 和 viewAdaptor 來實現(xiàn)的,這些對象從某種意義上來說就是公共接口 bind 方法的具體實現(xiàn)。

盡管 JavaScript 沒有提供接口類型來支持對象的 contract,但該對象的隱式接口依然能當(dāng)做一個 contract 提供給程序用戶。

ISP 與 JavaScript

我們下面討論的一些小節(jié)是 JavaScript 里關(guān)于違反接口隔離原則的影響。正如上面看到的,JavaScript 程序里實現(xiàn)接口隔離原則雖然可惜,但是不像靜態(tài)類型語言那樣強大,JavaScript 的語言特性有時候會使得所謂的接口搞得有點不粘性。

墮落的實現(xiàn)

在靜態(tài)類型語言語言里,導(dǎo)致違反 ISP 原則的一個原因是墮落的實現(xiàn)。在 Java 和 C#里所有的接口里定義的方法都必須實現(xiàn),如果你只需要其中幾個方法,那其他的方法也必須實現(xiàn)(可以通過空實現(xiàn)或者拋異常的方式)。在 JavaScript 里,如果只需要一個對象里的某一些接口的話,他也解決不了墮落實現(xiàn)這個問題,雖然不用強制實現(xiàn)上面的接口。但是這種實現(xiàn)依然違反了里氏替換原則。

var rectangle = {
    area: function() {
        /* 代碼 */
    },
    draw: function() {
        /* 代碼 */
    }
};
var geometryApplication = {
    getLargestRectangle: function(rectangles) {
        /* 代碼 */
    }
};
var drawingApplication = {
    drawRectangles: function(rectangles) {
       /* 代碼 */
    }
};

當(dāng)一個 rectangle 替代品為了滿足新對象 geometryApplication 的 getLargestRectangle 的時候,它僅僅需要 rectangl e的 area()方法,但它卻違反了 LSP(因為他根本用不到其中 drawRectangles 方法才能用到的 draw 方法)。

靜態(tài)耦合

靜態(tài)類型語言里的另外一個導(dǎo)致違反 ISP 的原因是靜態(tài)耦合,在靜態(tài)類型語言里,接口在一個松耦合設(shè)計程序里扮演了重大角色。不管是在動態(tài)語言還是在靜態(tài)語言,有時候一個對象都可能需要在多個客戶端用戶進行通信(比如共享狀態(tài)),對靜態(tài)類型語言,最好的解決方案是使用 Role Interfaces,它允許用戶和該對象進行交互(而該對象可能需要在多個角色)作為它的實現(xiàn)來對用戶和無關(guān)的行為進行解耦。在 JavaScript 里就沒有這種問題了,因為對象都被動態(tài)語言所特有的優(yōu)點進行解耦了。

語義耦合

導(dǎo)致違反 ISP 的一個通用原因,動態(tài)語言和靜態(tài)類型語言都有,那就是語義耦合,所謂語義耦合就是互相依賴,也就是一個對象的行為依賴于另外一個對象,那就意味著,如果一個用戶改變了其中一個行為,很有可能會影響另外一個使用用戶。這也違反單一職責(zé)原則了。可以通過繼承和對象替代來解決這個問題。

可擴展性

另外一個導(dǎo)致問題的原因是關(guān)于可擴展性,很多人在舉例的時候都會舉關(guān)于 callback 的例子用來展示可擴展性(比如 ajax 里成功以后的回調(diào)設(shè)置)。如果想這樣的接口需要一個實現(xiàn)并且這個實現(xiàn)的對象里有很多熟悉或方法的話,ISP 就會變得很重要了,也就是說當(dāng)一個接口 interface 變成了一個需求實現(xiàn)很多方法的時候,他的實現(xiàn)將會變得異常復(fù)雜,而且有可能導(dǎo)致這些接口承擔(dān)一個沒有粘性的職責(zé),這就是我們經(jīng)常提到的胖接口。

總結(jié)

JavaScript 里的動態(tài)語言特性,使得我們實現(xiàn)非粘性接口的影響力比靜態(tài)類型語言小,但接口隔離原則在 JavaScript 程序設(shè)計模式里依然有它發(fā)揮作用的地方。