本篇是 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