面向对象设计原则之开-闭原则
面向对象设计原则之里式替换原则
面向对象设计原则之依赖倒置原则
面向对象设计原则之单一职责原则
定义
里式替换原则(Liskov Substitution Principle),缩写为LSP,这个原则最早是在1986年由麻省理工学院计算机科学实验室的Barbara Liskov提出,她是这么描述这条原则的:
If S is a subtype of T, then objects of type T may be replaced with objects of type S, without breaking the program。
在1996年,Robert C.Martin(罗伯特C.马丁,鲍勃大叔)在他的SOLID(S单一职责 O开闭原则 L里式替换原则 I接口隔离原则 D依赖倒置原则)原则中,重新描述了这个原则:
Functions that use pointers of references to base classes must be able to use objects of derived classes without knowing it。
综合两者描述,我们将这条原则用中文描述出来为:程序中的对象可以在不改变程序正确性的前提下被它的子类所替换,即:子类可以替换任何父类能够出现的地方,并且经过替换后,代码还能正确工作。
根据LSP的定义,如果在程序中出现使用instanceof、强制类型转换或者函数覆盖,很可能意味着是对LSP的破坏。
里氏替换原则通俗来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。也就是说:子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。
作用
- 里氏替换原则是实现开闭原则的重要方式之一。它克服了继承中重写父类造成的可复用性变差的缺点。它是动作正确性的保证。即类的扩展不会给已有的系统引入新的错误,降低了代码出错的可能性。
- 加强程序的健壮性,同时变更时可以做到非常好的兼容性,提高程序的维护性、可扩展性,降低需求变更时引入的风险
实现方法
里氏替换原则通俗来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。也就是说:子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。根据上述理解,对里氏替换原则的定义可以总结如下:
- 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
- 子类中可以增加自己特有的方法。
- 当子类的方法重载父类的方法时,方法的前置条件(即方法的输入参数)要比父类的方法更宽松。
- 当子类的方法实现父类的方法时(重写/重载或实现抽象方法),方法的后置条件(即方法的的输出/返回值)要比父类的方法更严格或相等。
代码示例
以鸵鸟是不是鸟为例,鸵鸟是一种鸟,但是不会飞。
1.我们创建鸟类,有个飞行的方法,然后鸵鸟和麻雀分别继承鸟类,但是鸵鸟由于不会飞,所以需要重写父类的飞行方法。
package will.tools.design.lsp.a;
public class Birds {
public void fly() {
System.out.println("I am flying");
}
}
package will.tools.design.lsp.a;
public class Sparrow extends Birds {
@Override
public void fly() {
super.fly();
}
}
package will.tools.design.lsp.a;
public class Ostrich extends Birds {
@Override
public void fly() {
System.out.println("i can not fly");
}
}
这时我们看到,在鸵鸟的类中,我们重写了飞行方法。我们测试一下,
public static void main(String[] args){
Birds birds = new Birds();
birds.fly();
Birds sparrow = new Sparrow();
sparrow.fly();
Birds ostrich = new Ostrich();
ostrich.fly();
}
I am flying
I am flying
i can not fly
我们发现,创建父类对象new Birds的地方,我们用Sparrow来替换,没有问题,但是用Ostrich来替换,运行结果就已经变了。这其实就是我们设计鸵鸟类的时候,继承鸟类这里已经违反了里式替换原则。
怎么修改呢,一种方式我们认为鸵鸟和鸟还是有继承关系存在的(鸵鸟是鸟),把无法继承的飞行的行为抽象为接口,Birds类中不再有飞行方法。麻雀和鸵鸟都继承鸟的基础信息,但是根据业务规则,麻雀实现飞行的接口,鸵鸟不实现飞行接口。另一种方式是,我们认为鸵鸟和鸟不再有继承关系(鸵鸟不是鸟),这是我们抽象一个动物类,有一个move方法,鸟类和鸵鸟都继承动物类(鸟类的移动是飞,鸵鸟的移动是跑,但都是move),麻雀继承鸟类。
我们以第二种方式类修改代码如下
package will.tools.design.lsp.b;
public class Animal {
public void move() {
System.out.println("I am moving");
}
}
package will.tools.design.lsp.b;
public class Birds extends Animal{
@Override
public void move() {
super.move();
}
}
package will.tools.design.lsp.b;
public class Ostrich extends Animal {
@Override
public void move() {
super.move();
}
}
package will.tools.design.lsp.b;
public class Sparrow extends Birds{
@Override
public void move() {
super.move();
}
}
public static void main(String[] args){
Animal animal = new Animal();
animal.move();
Animal birds = new Birds();
birds.move();
Animal sparrow = new Sparrow();
sparrow.move();
Animal ostrich = new Ostrich();
ostrich.move();
}
这是我们运行验证,结果为:
I am moving
I am moving
I am moving
I am moving
这时,我们出现父类对象new Animal()的地方,使用 Birds,Sparrow,Ostrich 来替换,运行结果都是一致的,都没有问题