欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

关于里氏替换原则(LSP)

程序员文章站 2024-01-14 14:26:04
...

里氏替换原则(LSP)的定义

如果对每一个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象o1都替换成o2时,程序P的行为没有变化,那么类型T2是类型T1的子类型。
也就是说,所有引用基类的地方必须能透明地使用其子类的对象。
1、子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
2、子类中可以增加自己特有的方法。具体体现:
子类必须实现父类所有非私有的属性和方法,或子类的所有非私有属性和方法必须在父类中声明。即,子类可以有自己的“个性”,这也就是说,里氏代换原则可以正着用,不能反着用(在项目中,采用里氏替换原则时,尽量避免子类的“个性”,一旦子类有“个性”,这个子类和父类之间的关系就很难调和了)。根据里氏代换原则,为了保证系统的扩展性,在程序中通常使用父类来进行定义,如果一个方法只存在子类中,在父类中不提供相应的声明,则无法在以父类定义的对象中使用该方法。尽量把父类设计为抽象类或者接口。让子类继承父类或实现父接口,并实现在父类中声明的方法,运行时,子类实例替换父类实例,我们可以很方便地扩展系统的功能,同时无须修改原有子类的代码,增加新的功能可以通过增加一个新的子类来实现。

里氏替换原则的要求

子类可以实现父类的抽象方法,当不能覆盖子类的非抽象方法。
子类中可以增加自己特有的方法。
当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。

例子

我们写了一个针对所有车型的抽象类,限制了速度的上限。并且定义了一个方法,刹车。对于该方法,要求前置条件输入必须为0,后置条件要求速度必须对比之前降低。

abstract class Vehicle {
 
 
 int speed, limit;
 //@ invariant speed < limit;
 //@ requires speed != 0;
 //@ ensures speed < \old(speed)
 void brake();
}

接着我们实现一个子类汽车继承该抽象类

class Car extends Vehicle {
 int fuel;
 boolean engineOn;
 //@ invariant speed < limit;
 //@ invariant fuel >= 0; 
 //@ requires fuel > 0 && !engineOn;
 //@ ensures engineOn;
 void start() {}
 void accelerate() {}
 //@ requires speed != 0;
 //@ ensures speed < \old(speed)
 void brake() {}
}

可以看出,在汽车这个子类中,它的不变性相对于父类增强了。前置条件和后置条件的强度并没有改变,这符合LSP原则的定义。
接下来,再看下面的例子
还是以车为例

class Car extends Vehicle {
 int fuel;
 boolean engineOn;
 //@ invariant speed < limit;
 //@ invariant fuel >= 0;
 //@ requires fuel > 0 && !engineOn;
 //@ ensures engineOn;
 void start() {}
 void accelerate() {}
 //@ requires speed != 0;
 //@ ensures speed < \old(speed)
 void brake() {}
}
class Hybrid extends Car {
 int charge;
 //@ invariant charge >= 0;
 //@ requires (charge > 0 
|| fuel > 0) && !engineOn;
 //@ ensures engineOn;
 void start() {}
 void accelerate() {}
 //@ requires speed != 0;
 //@ ensures speed < \old(speed)
 //@ ensures charge > \old(charge)
 void brake() {}
}

在这个例子中,子类混合动力车继承自汽车类,可以看到子类重写了引擎方法。在父类中要求前置条件必须保证有原料。但是在子类中放宽了条件,只需要保证有燃料或者有电能即可。这符合LSP原则。同时可以发现,对于刹车方法后置条件增强了。也符合LSP原则。

为什么要符合LSP

在项目中,采用LSP原则,可以有效的避免子类的“个性”,一旦子类有“个性”,子类和父类之间的关系就很难调和,采用LSP原则可以提高代码的健壮性、复用性、降低耦合性以及便于扩充功能。