前端内参
搜索文档…
陆.1.4 里氏替换原则

01.什么是里氏替换原则

里氏替换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。
里氏替换原则中说,任何基类可以出现的地方,子类一定可以出现。
LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。
我们一般对里氏替换原则 LSP 的解释为:
子类对象能够替换父类对象,而程序逻辑不变。

02.里氏替换原则的两种含义:

1).含义一

里氏替换原则是针对继承而言的,如果继承是为了实现代码重用,也就是为了共享方法,那么共享的父类方法就应该保持不变,不能被子类重新定义。子类只能通过新添加方法来扩展功能,父类和子类都可以实例化,而子类继承的方法和父类是一样的,父类调用方法的地方,子类也可以调用同一个继承得来的,逻辑和父类一致的方法,这时用子类对象将父类对象替换掉时,当然逻辑一致,相安无事。

2).含义二

如果继承的目的是为了多态,而多态的前提就是子类覆盖并重新定义父类的方法,为了符合LSP,我们应该将父类定义为抽象类,并定义抽象方法,让子类重新定义这些方法,当父类是抽象类时,父类就是不能实例化,所以也不存在可实例化的父类对象在程序里。也就不存在子类替换父类实例(根本不存在父类实例了)时逻辑不一致的可能。
不符合LSP的最常见的情况是,父类和子类都是可实例化的非抽象类,且父类的方法被子类重新定义,这一类的实现继承会造成父类和子类间的强耦合,也就是实际上并不相关的属性和方法牵强附会在一起,不利于程序扩展和维护。
在面向对象编程领域,根据我的经验可以总结一句话 : 尽量不要从可实例化的父类中继承,而是要使用基于抽象类和接口的继承”。

03.JavaScript代码示例

在JavaScript领域,因为JavaScript实在太“宽松”了,几乎想怎么写就怎么写,所以并不是特别好演示里氏替换原则。这里用一个简单的例子来粗浅地演示一下:
1
//父类
2
class Shape {
3
get area() {
4
return 0;
5
}
6
}
7
8
//子类
9
class Rectangle extends Shape {
10
constructor(length, width) {
11
super();
12
this.length = length;
13
this.width = width;
14
} get area() {
15
return this.length * this.width;
16
}
17
}
18
//子类
19
class Square extends Shape {
20
constructor(length) {
21
super();
22
this.length = length;
23
} get area() {
24
return this.length ** 2;
25
}
26
}
27
//子类
28
class Circle extends Shape {
29
constructor(radius) {
30
super();
31
this.radius = radius;
32
} get area() {
33
return Math.PI * (this.radius ** 2);
34
}
35
}
36
37
//这里是父类hape的数组,但是数组具体的项存放的是子类的实例
38
const shapes = [
39
new Rectangle(1, 2),
40
new Square(1, 2),
41
new Circle(2),
42
]
43
44
for (let s of shapes) {
45
//本来要调用父类的area,但是现在调用子类的area。
46
//即使调用子类的area,也不会影响软件的整体功能。
47
//也即“子类对象能够替换父类对象,而程序逻辑不变”。
48
console.log(s.area);
49
}
Copied!
最近更新 1yr ago