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

鍍金池/ 教程/ HTML/ S.O.L.I.D 五大原則之里氏替換原則 LSP
代碼復(fù)用模式(避免篇)
S.O.L.I.D 五大原則之接口隔離原則 ISP
設(shè)計(jì)模式之狀態(tài)模式
JavaScript 核心(晉級高手必讀篇)
設(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)
對象創(chuàng)建模式(上篇)
This? Yes,this!
設(shè)計(jì)模式之代理模式
變量對象(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 對象”這回事!
JavaScript 與 DOM(下)
面向?qū)ο缶幊讨?ECMAScript 實(shí)現(xiàn)
全面解析 Module 模式
對象創(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 五大原則之里氏替換原則 LSP

前言

本章我們要講解的是 S.O.L.I.D 五大原則 JavaScript 語言實(shí)現(xiàn)的第3篇,里氏替換原則 LSP(The Liskov Substitution Principle )。

開閉原則的描述是:

Subtypes must be substitutable for their base types.
派生類型必須可以替換它的基類型。

在面向?qū)ο缶幊汤?,繼承提供了一個(gè)機(jī)制讓子類和共享基類的代碼,這是通過在基類型里封裝通用的數(shù)據(jù)和行為來實(shí)現(xiàn)的,然后已經(jīng)及類型來聲明更詳細(xì)的子類型,為了應(yīng)用里氏替換原則,繼承子類型需要在語義上等價(jià)于基類型里的期望行為。

為了來更好的理解,請參考如下代碼:

function Vehicle(my) {
    var my = my || {};
    my.speed = 0;
    my.running = false;
    this.speed = function() {
        return my.speed;
    };
    this.start = function() {
        my.running = true;
    };
    this.stop = function() {
        my.running = false;
    };
    this.accelerate = function() {
        my.speed++;
    };
    this.decelerate = function() {
        my.speed--;
    }, this.state = function() {
        if (!my.running) {
            return "parked";
        }
        else if (my.running && my.speed) {
            return "moving";
        }
        else if (my.running) {
            return "idle";
        }
    };
}

上述代碼我們定義了一個(gè) Vehicle 函數(shù),其構(gòu)造函數(shù)為 vehicle 對象提供了一些基本的操作,我們來想想如果當(dāng)前函數(shù)當(dāng)前正運(yùn)行在服務(wù)客戶的產(chǎn)品環(huán)境上,如果現(xiàn)在需要添加一個(gè)新的構(gòu)造函數(shù)來實(shí)現(xiàn)加快移動(dòng)的 vehicle。思考以后,我們寫出了如下代碼:

function FastVehicle(my) {
    var my = my || {};
    var that = new Vehicle(my);
    that.accelerate = function() {
        my.speed += 3;
    };
    return that;
}

在瀏覽器的控制臺我們都測試了,所有的功能都是我們的預(yù)期,沒有問題,F(xiàn)astVehicle 的速度增快了 3 倍,而且繼承他的方法也是按照我們的預(yù)期工作。此后,我們開始部署這個(gè)新版本的類庫到產(chǎn)品環(huán)境上,可是我們卻接到了新的構(gòu)造函數(shù)導(dǎo)致現(xiàn)有的代碼不能支持執(zhí)行了,下面的代碼段揭示了這個(gè)問題:

var maneuver = function(vehicle) {
    write(vehicle.state());
    vehicle.start();
    write(vehicle.state());
    vehicle.accelerate();
    write(vehicle.state());
    write(vehicle.speed());
    vehicle.decelerate();
    write(vehicle.speed());
    if (vehicle.state() != "idle") {
        throw "The vehicle is still moving!";
    }
    vehicle.stop();
    write(vehicle.state());
};

根據(jù)上面的代碼,我們看到拋出的異常是“The vehicle is still moving!”,這是因?yàn)閷戇@段代碼的作者一直認(rèn)為加速(accelerate)和減速(decelerate)的數(shù)字是一樣的。但 FastVehicle 的代碼和 Vehicle 的代碼并不是完全能夠替換掉的。因此,F(xiàn)astVehicle 違反了里氏替換原則。

在這點(diǎn)上,你可能會(huì)想:“但,客戶端不能老假定 vehicle 都是按照這樣的規(guī)則來做”,里氏替換原則(LSP)的妨礙(譯者注:就是妨礙實(shí)現(xiàn) LSP 的代碼)不是基于我們所想的繼承子類應(yīng)該在行為里確保更新代碼,而是這樣的更新是否能在當(dāng)前的期望中得到實(shí)現(xiàn)。

上述代碼這個(gè) case,解決這個(gè)不兼容的問題需要在 vehicle 類庫或者客戶端調(diào)用代碼上進(jìn)行一點(diǎn)重新設(shè)計(jì),或者兩者都要改。

減少 LSP 妨礙

那么,我們?nèi)绾伪苊?LSP 妨礙?不幸的話,并不是一直都是可以做到的。我們這里有幾個(gè)策略我們處理這個(gè)事情。

契約(Contracts)

處理 LSP 過分妨礙的一個(gè)策略是使用契約,契約清單有 2 種形式:執(zhí)行說明書(executable specifications)和錯(cuò)誤處理,在執(zhí)行說明書里,一個(gè)詳細(xì)類庫的契約也包括一組自動(dòng)化測試,而錯(cuò)誤處理是在代碼里直接處理的,例如在前置條件,后置條件,常量檢查等,可以從 Bertrand Miller 的大作《契約設(shè)計(jì)》中查看這個(gè)技術(shù)。雖然自動(dòng)化測試和契約設(shè)計(jì)不在本篇文字的范圍內(nèi),但當(dāng)我們用的時(shí)候我還是推薦如下內(nèi)容:

  1. 檢查使用測試驅(qū)動(dòng)開發(fā)(Test-Driven Development)來指導(dǎo)你代碼的設(shè)計(jì)
  2. 設(shè)計(jì)可重用類庫的時(shí)候可隨意使用契約設(shè)計(jì)技術(shù)

對于你自己要維護(hù)和實(shí)現(xiàn)的代碼,使用契約設(shè)計(jì)趨向于添加很多不必要的代碼,如果你要控制輸入,添加測試是非常有必要的,如果你是類庫作者,使用契約設(shè)計(jì),你要注意不正確的使用方法以及讓你的用戶使之作為一個(gè)測試工具。

避免繼承

避免 LSP 妨礙的另外一個(gè)測試是:如果可能的話,盡量不用繼承,在Gamma的大作《Design Patterns – Elements of Reusable Object-Orineted Software》中,我們可以看到如下建議:

Favor object composition over class inheritance
盡量使用對象組合而不是類繼承

有些書里討論了組合比繼承好的唯一作用是靜態(tài)類型,基于類的語言(例如,在運(yùn)行時(shí)可以改變行為),與 JavaScript 相關(guān)的一個(gè)問題是耦合,當(dāng)使用繼承的時(shí)候,繼承子類型和他們的基類型耦合在一起了,就是說基類型的改變會(huì)影響到繼承子類型。組合傾向于對象更小化,更容易向靜態(tài)和動(dòng)態(tài)語言語言維護(hù)。

與行為有關(guān),而不是繼承

到現(xiàn)在,我們討論了和繼承上下文在內(nèi)的里氏替換原則,指示出 JavaScript 的面向?qū)ο髮?shí)。不過,里氏替換原則(LSP)的本質(zhì)不是真的和繼承有關(guān),而是行為兼容性。JavaScript 是一個(gè)動(dòng)態(tài)語言,一個(gè)對象的契約行為不是對象的類型決定的,而是對象期望的功能決定的。里氏替換原則的初始構(gòu)想是作為繼承的一個(gè)原則指南,等價(jià)于對象設(shè)計(jì)中的隱式接口。

舉例來說,讓我們來看一下 Robert C. Martin的 大作《敏捷軟件開發(fā) 原則、模式與實(shí)踐》中的一個(gè)矩形類型:

矩形例子

考慮我們有一個(gè)程序用到下面這樣的一個(gè)矩形對象:

var rectangle = {
    length: 0,
    width: 0
};

過后,程序有需要一個(gè)正方形,由于正方形就是一個(gè)長(length)和寬(width)都一樣的特殊矩形,所以我們覺得創(chuàng)建一個(gè)正方形代替矩形。我們添加了 length 和 width 屬性來匹配矩形的聲明,但我們覺得使用屬性的g etters/setters 一般我們可以讓 length 和 width 保存同步,確保聲明的是一個(gè)正方形:

var square = {};
(function() {
    var length = 0, width = 0;
    // 注意defineProperty方式是262-5版的新特性
    Object.defineProperty(square, "length", {
        get: function() { return length; },
        set: function(value) { length = width = value; }
    });
    Object.defineProperty(square, "width", {
        get: function() { return width; },
        set: function(value) { length = width = value; }
    });
})();

不幸的是,當(dāng)我們使用正方形代替矩形執(zhí)行代碼的時(shí)候發(fā)現(xiàn)了問題,其中一個(gè)計(jì)算矩形面積的方法如下:

var g = function(rectangle) {
    rectangle.length = 3;
    rectangle.width = 4;
    write(rectangle.length);
    write(rectangle.width);
    write(rectangle.length * rectangle.width);
};

該方法在調(diào)用的時(shí)候,結(jié)果是 16,而不是期望的 12,我們的正方形 square 對象違反了 LSP 原則,square 的長度和寬度屬性暗示著并不是和矩形 100% 兼容,但我們并不總是這樣明確的暗示。解決這個(gè)問題,我們可以重新設(shè)計(jì)一個(gè) shape 對象來實(shí)現(xiàn)程序,依據(jù)多邊形的概念,我們聲明 rectangle 和square,relevant。不管怎么說,我們的目的是要說里氏替換原則并不只是繼承,而是任何方法(其中的行為可以另外的行為)。

總結(jié)

里氏替換原則(LSP)表達(dá)的意思不是繼承的關(guān)系,而是任何方法(只要該方法的行為能體會(huì)另外的行為就行)。