什么是里氏替换原则?
里氏替换原则(Liskov Substitution Principle,LSP)指的是在任何父类出现的地方子类也一定可以出现,也就是说一个优秀的软件设计中有引用父类的地方,一定也可以替换为其子类。阐述了有关继承的一些原则,也就是什么时候应该使用继承,什么时候不应该使用继承,以及其中蕴含的原理。
里氏替换原则作用?
- 里氏替换原则是实现开闭原则的重要方式之一。
- 它克服了继承中重写父类造成的可复用性变差的缺点。
- 它是动作正确性的保证。即类的扩展不会给已有的系统引入新的错误,降低了代码出错的可能性。
- 加强程序的健壮性,同时变更时可以做到非常好的兼容性,提高程序的维护性、可扩展性,降低需求变更时引入的风险。
案例分析
鱼可以在水里游,种类也非常的多,比如金鱼、鲫鱼、鲤鱼等。
方案一:初始设计
1、定义鱼的抽象类,因为鱼都可以游泳,所以定义一个游泳的抽象方法
2、定义具体鱼类并继承鱼的抽象类并实现游的方法
1、定义鱼的抽象类
1
2
3
4
| abstract class Fish {
// 游
void swim();
}
|
2、定义具体鱼类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| class GoldFish extends Fish {
@override
void swim() {
print("金鱼在游泳");
}
}
class CarpFish extends Fish {
@override
void swim() {
print("鲤鱼在游泳");
}
}
|
3、使用
1
2
3
4
5
6
7
| void main(List<String> args) {
Fish goldFish = GoldFish();
goldFish.swim();
Fish carpFish = CarpFish();
carpFish.swim();
}
|
4、运行结果
1
2
3
4
| Connecting to VM Service at http://127.0.0.1:59459/DrI2gWzRnMY=/
金鱼在游泳
鲤鱼在游泳
Exited
|
5、完整代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| void main(List<String> args) {
Fish goldFish = GoldFish();
goldFish.swim();
Fish carpFish = CarpFish();
carpFish.swim();
}
abstract class Fish {
// 游
void swim();
}
class GoldFish extends Fish {
@override
void swim() {
print("金鱼在游泳");
}
}
class CarpFish extends Fish {
@override
void swim() {
print("鲤鱼在游泳");
}
}
|
6、分析
至此,代码很完美的实现了需求,但如果新增一个气球鱼,那么很明显就会出现问题,因为气球鱼不会游泳,那么针对这类需求我们就还继续继承自Fish了,就违背了里氏替换原则,下面我们来看看如何改造
设计优化方案—动物抽象类
由于气球鱼和其他鱼类都属于动物类,我们新增一个动物抽象类并且有一个移动的方法,让鱼的抽象类直接继承动物类,而气球鱼直接继承动物类即可,具体代码如下:
1、定义动物抽象类
1
2
3
4
| abstract class Animal {
// 移动
void move();
}
|
2、定义鱼抽象类并继承动物抽象类
1
2
3
4
| abstract class Fish extends Animal {
// 游
void swim();
}
|
3、定义气球鱼类并继承动物抽象类
1
2
3
4
5
6
| class BalloonFish extends Animal {
@override
void move() {
print("气球鱼在移动");
}
}
|
4、定义具体鱼类并继承鱼的抽象类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| class GoldFish extends Fish {
@override
void swim() {
print("金鱼在游泳");
}
@override
void move() {
print("金鱼在移动");
}
}
class CarpFish extends Fish {
@override
void swim() {
print("鲤鱼在游泳");
}
@override
void move() {
print('鲤鱼在移动');
}
}
|
5、使用
1
2
3
4
5
6
7
8
9
10
11
12
| void main(List<String> args) {
Fish goldFish = GoldFish();
goldFish.swim();
goldFish.move();
Fish carpFish = CarpFish();
carpFish.swim();
carpFish.move();
Animal balloonFish = BalloonFish();
balloonFish.move();
}
|
6、运行结果
1
2
3
4
5
6
7
| Connecting to VM Service at http://127.0.0.1:60636/G4yLu1q9KsU=/
金鱼在游泳
金鱼在移动
鲤鱼在游泳
鲤鱼在移动
气球鱼在移动
Exited
|
7、完整代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
| void main(List<String> args) {
Fish goldFish = GoldFish();
goldFish.swim();
goldFish.move();
Fish carpFish = CarpFish();
carpFish.swim();
carpFish.move();
Animal balloonFish = BalloonFish();
balloonFish.move();
}
abstract class Animal {
// 移动
void move();
}
abstract class Fish extends Animal {
// 游
void swim();
}
class GoldFish extends Fish {
@override
void swim() {
print("金鱼在游泳");
}
@override
void move() {
print("金鱼在移动");
}
}
class CarpFish extends Fish {
@override
void swim() {
print("鲤鱼在游泳");
}
@override
void move() {
print('鲤鱼在移动');
}
}
class BalloonFish extends Animal {
@override
void move() {
print("气球鱼在移动");
}
}
|
8、分析
我们通过简单的重构加强程序的健壮性,当我们不管有什么鱼类出现的时候,继承自对应的类或者新增对应的方法或者属性即可,有效的降低了需求变更时引入的风险。
总结
在我们的实际开发过程中应该多去多方面考虑业务将可能出现的扩展性,这样我们在设计接口的时候能提供更好的兼容性,降低代码出错的可能性,同时也提高了程序的维护性、可扩展性,降低需求变更时引入的风险。