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

鍍金池/ 教程/ HTML/ S.O.L.I.D 五大原則之里氏替換原則 LSP
代碼復(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ì)模式之原型模式
根本沒(méi)有“JSON 對(duì)象”這回事!
JavaScript 與 DOM(下)
面向?qū)ο缶幊讨?ECMAScript 實(shí)現(xiàn)
全面解析 Module 模式
對(duì)象創(chuàng)建模式(下篇)
設(shè)計(jì)模式之職責(zé)鏈模式
S.O.L.I.D 五大原則之開(kāi)閉原則 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 語(yǔ)言實(shí)現(xiàn)的第3篇,里氏替換原則 LSP(The Liskov Substitution Principle )。

開(kāi)閉原則的描述是:

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

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

為了來(lái)更好的理解,請(qǐng)參考如下代碼:

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 對(duì)象提供了一些基本的操作,我們來(lái)想想如果當(dāng)前函數(shù)當(dāng)前正運(yùn)行在服務(wù)客戶的產(chǎn)品環(huán)境上,如果現(xiàn)在需要添加一個(gè)新的構(gòu)造函數(shù)來(lái)實(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;
}

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

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ī)則來(lái)做”,里氏替換原則(LSP)的妨礙(譯者注:就是妨礙實(shí)現(xiàn) LSP 的代碼)不是基于我們所想的繼承子類應(yīng)該在行為里確保更新代碼,而是這樣的更新是否能在當(dāng)前的期望中得到實(shí)現(xiàn)。

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

減少 LSP 妨礙

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

契約(Contracts)

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

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

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

避免繼承

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

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

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

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

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

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

矩形例子

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

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

過(guò)后,程序有需要一個(gè)正方形,由于正方形就是一個(gè)長(zhǎng)(length)和寬(width)都一樣的特殊矩形,所以我們覺(jué)得創(chuàng)建一個(gè)正方形代替矩形。我們添加了 length 和 width 屬性來(lái)匹配矩形的聲明,但我們覺(jué)得使用屬性的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)了問(wè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 對(duì)象違反了 LSP 原則,square 的長(zhǎng)度和寬度屬性暗示著并不是和矩形 100% 兼容,但我們并不總是這樣明確的暗示。解決這個(gè)問(wèn)題,我們可以重新設(shè)計(jì)一個(gè) shape 對(duì)象來(lái)實(shí)現(xiàn)程序,依據(jù)多邊形的概念,我們聲明 rectangle 和square,relevant。不管怎么說(shuō),我們的目的是要說(shuō)里氏替換原則并不只是繼承,而是任何方法(其中的行為可以另外的行為)。

總結(jié)

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