乐趣区

关于javascript:JavaScript-中的-SOLID-原则三L代表什么

本篇是 SOLID 准则的第三局部,倡议先浏览前两局部:

JavaScript 中的 SOLID 准则:“S”代表什么

JavaScript 中的 SOLID 准则:“O”代表什么

这是 SOLID 的第三篇文章(原文一共五篇),作者是 serhiirubets,欢送继续关注。

里氏替换准则(Liskov Substitution Principle)

L – 里氏替换准则。这个准则是指:如果 S 是 T 的子类型,那么程序中的 T 对象能够被 S 对象替换,不须要改变程序中任何所需属性。从定义上可能没有方法清晰的了解其含意,咱们略微换一个说法:应用指针或援用基类的函数必须能够替换为其派生类。// todo

让咱们用更简略的形式来形容它,例如:你有一个“Car”类,并且在不同中央进行了应用。这个准则的意思是:每一个应用 Car 类的中央,都应该能够被 Car 类的子类替换。如果咱们有一个继承自“Car“的“Passenger Car”, 或者有一个“SUV”类也继承自“Car“,如果咱们把“Car”类替换成“SUV”类或者“Passenger Car”类,即把父类 Car 替换成任何一个子类后,咱们的零碎应该像以前一样失常工作。

举个简略的例子,咱们有一个“Rectangle”(矩形)类,因为”正方形“也是“矩形”,咱们能够创立一个根本的“Rectangle”类和“Square”类,“Square”继承自“Rectangle”。

class Rectangle {constructor(width,height) {
        this.width = width
        this.height = height
    }
    setWidth(width) {this.width = width}
    setHeight(height) {this.height = height}
    getArea() {return this.width * this.height}
}
// Square 计算面积的形式有点不同,它的高度和宽度一样的, 重写 setWidth 和 setHeight 办法。class Square extends Rectangle {setWidth(width) {
        this.width = width;
        this.height = width;
    }
    setHeight(height) {
        this.width = height;
        this.height = height;
    }
}
const rectangleFirst = new Rectangle(10, 15)
const rectangleSecond = new Rectangle(5, 10)

console.log(rectangleFirst.getArea()); // 150
console.log(rectangleSecond.getArea()); // 50

rectangleFirst.setWidth(20)
rectangleSecond.setWidth(15)

console.log(rectangleFirst.getArea()); // 300
console.log(rectangleSecond.getArea()); // 150

咱们创立了两个实例,查看了矩形面积,更改宽高并再次查看了面积,咱们看到一切正常,代码按预期工作,然而,让咱们再看一下 里氏替换准则:如果咱们更改任何子类的基类,咱们的零碎应该像以前一样工作。

const rectangleFirst = new Square(10, 15)
const rectangleSecond = new Square(5, 10)

console.log(rectangleFirst.getArea()); // 150
console.log(rectangleSecond.getArea()); // 50

rectangleFirst.setWidth(20)
rectangleSecond.setWidth(15)

console.log(rectangleFirst.getArea()); // 400
console.log(rectangleSecond.getArea()); // 225

咱们把 new Rectangle() 替换为new Square() 后发现,在 setWidth 之后, getArea返回了和替换之前不同的值,很显著咱们没有遵循里氏替换准则。

那么咱们应该怎么解决呢?解决方案是应用继承,但不是从”Rectangle“类,而是筹备一个更“正确”的类。比方,咱们创立一个“Sharp”类,它只负责计算面积:

class Shape {getArea() {return this.width * this.height;}
}
class Rectangle {constructor(width,height) {super();
        this.width = width
        this.height = height
    }
    setWidth(width) {this.width = width}
    setHeight(height) {this.height = height}
}
class Square extends Shape {setWidth(width) {
        this.width = width;
        this.height = width;
    }
    setHeight(height) {
        this.width = height;
        this.height = height;
    }
}

咱们创立了一个更通用的基类 Shape,在应用new Shape() 的中央咱们都能够把 Shape 批改为任何它的子类,而不会毁坏原有逻辑。

在咱们的示例中,Rectangle 和 Square 是不同的对象,它们蕴含了一些类似的逻辑,但也有不同的逻辑,所以把他们离开而不是用作“父子”类会更正确。

咱们再来看一个对了解这个准则有帮忙的例子:

咱们要创立一个 Bird 类,咱们正在思考应该增加什么办法,从第一个角度来看,咱们能够思考增加 fly 办法,因为所有的鸟都会飞。

class Bird{fly(){}}
function allFly(birds) {birds.forEach(bird => bird.fly())
}
allFly([new Bird(), new Bird(), new Bird()])

之后,咱们意识到存在不同的鸟类:鸭子、鹦鹉、天鹅。

class Duck extends Bird {quack(){}}
class Parrot extends Bird {repeat(){}}
class Swan extends Bird{beBeautiful(){}}

当初,里氏替换准则说,如果咱们把基类更改为子类,零碎应该像以前一样工作:

class Duck extends Bird {quack(){}}
class Parrot extends Bird {repeat(){}}
class Swan extends Bird{beBeautiful(){}}
function allFly(birds){birds.forEach(bird=> bird.fly())
}
allFly([new Duck(), new Parrot(), new Swan()])

咱们在调用 allFly 函数时,扭转了参数,咱们调用了new Duck(),new Parrot(),new Swan(), 而不是调用new Bird()。一切正常,咱们正确的遵循了里氏替换准则。

当初咱们想再增加一只企鹅,然而企鹅并不会飞,如果想调用 fly 办法,咱们就抛出一个谬误。

class Penguin extends Bird {fly(){throw new Error('Sorry, but I cannot fly')
  }
  swim(){}
}
allFly([new Duck(), new Parrot(), new Swan(), new Penguin()])

然而咱们遇到一个问题:fly 办法并不冀望呈现外部谬误,allFly 办法也只是为会飞的鸟创立的,企鹅不会飞,所以咱们违反了里氏替换准则。

怎么解决这个问题?与其创立一个根本的 Bird 类,不如创立一个 FlyingBird 类,所有会飞的鸟都只继承自 FlyingBird 类,allFly 办法也只承受Flying Bird

class Bird{
}
class FlyingBird{fly(){}}
class Duck extends FlyingBird {quack(){}}
class Parrot extends FlyingBird {repeat(){}}
class Swan extends FlyingBird{beBeautiful(){}}
class Penguin extends Bird {swim(){}}

Penguin继承自 Bird 类,而不是 FlyingBird 类,咱们也不须要调用会引发谬误的 fly 办法。在任何调用 FlyingBird 的中央,能够间接换成更具体的鸟类,比方 Duck、Parrot、Swan,代码也会失常工作。

心愿你能够通过本文可能更好的了解 里氏替换准则,理解在 JavaScript 是如何工作的和如何在我的项目中应用。下一篇中,咱们将持续学习 SOLID 中的下一个 ’L’ 字母

欢送关注微信公众号”混沌前端“

举荐浏览:

基于 TypeScript 了解程序设计的 SOLID 准则

clean-code-javascript: SOLID

退出移动版