本篇是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()); // 150console.log(rectangleSecond.getArea()); // 50rectangleFirst.setWidth(20)rectangleSecond.setWidth(15)console.log(rectangleFirst.getArea()); // 300console.log(rectangleSecond.getArea()); // 150

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

const rectangleFirst = new Square(10, 15)const rectangleSecond = new Square(5, 10)console.log(rectangleFirst.getArea()); // 150console.log(rectangleSecond.getArea()); // 50rectangleFirst.setWidth(20)rectangleSecond.setWidth(15)console.log(rectangleFirst.getArea()); // 400console.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