里氏代换原则
1、里氏代换原则介绍
里氏代换原则(Liskov Substitution Principle, LSP):子类型必须能够替换掉它们的父类型。
一个软件实体如果使用的是一个父类的话,那么一定适用于其子类,而且它察觉不出父类对象和子类对象的区别。也就是说,在软件里面,把父类都替换成它的子类,程序的行为没有变化,简单地说,子类型必须能够替换掉它们的父类型。
这个原则主要是为了保证在使用继承时,子类能够替换掉父类,并且在不影响系统原有功能的前提下,增加新的功能。也就是说,如果一个程序是建立在一个父类的基础上,那么通过替换成其子类,程序仍然能够正常运行,并达到预期的结果。
里氏代换原则的关键在于子类重写父类的方法必须保证父类所定义的方法的前置条件、后置条件以及副作用在子类中仍然成立。也就是说,子类对父类的方法进行重写时,不应该改变原有的约定、规范和契约,否则可能会破坏原有的功能。
2、案例说明
class Rectangle { protected int width = 0; protected int height = 0; public void setWidth(int width) { this.width = width; } public void setHeight(int height) { this.height = height; } public int getWidth() { return width; } public int getHeight() { return height; } public int getArea() { return width * height; } } class Square extends Rectangle { public void setWidth(int width) { this.width = width; this.height = width; } public void setHeight(int height) { this.width = height; this.height = height; } } public class Main { public static void main(String[] args) { Rectangle r = new Square(); r.setWidth(5); r.setHeight(10); System.out.println(r.getArea()); } }
在这个程序中,我们定义了一个矩形类和它的子类正方形。正方形是矩形的一种特殊情况,只不过长和宽相等而已。显然,正方形可以替换矩形的位置,但是,在这个例子中,如果我们以正方形对象来创建一个矩形对象的话,就会出现问题。因为矩形的宽和高是可以分别设置的,而正方形必须保证宽和高相等。由于正方形重写了父类矩形的设置宽、高的方法,因此矩形作为父类被替换成正方形的时候,就会出现宽高不符合期望的情况。
这个例子违反了LSP原则,因为父类与子类之间的行为不是完全一致的,父类中允许的某些操作在子类中并不适用。
3、使用LSP改进代码
abstract class Quadrilateral{ protected int width = 0; protected int height = 0; public void setWidth(int width) { this.width = width; } public void setHeight(int height) { this.height = height; } public int getWidth() { return width; } public int getHeight() { return height; } public abstract int getArea(); } class Rectangle extends Quadrilateral { public int getArea() { return width * height; } } class Square extends Quadrilateral { public void setWidth(int side) { this.width = side; this.height = side; } public void setHeight(int side) { this.width = side; this.height = side; } public int getArea() { return width * height; } } public class Main { public static void main(String[] args) { Quadrilateral q1 = new Rectangle(); q1.setWidth(5); q1.setHeight(10); Quadrilateral q2 = new Square(); q2.setWidth(5); System.out.println(q1.getArea()); System.out.println(q2.getArea()); } }
在这个程序中,我们定义了一个抽象的四边形类Quadrilateral,并且让Rectangle和Square分别继承这个类。因为两个类都共享了它们所包含的行为(getArea)。Square类重写了setWidth和setHeight方法以确保高度和宽度相等。这样,我们就能够根据需要使用Rectangle或Square对象来进行运算而不会改变程序的表现和结果。
这个程序遵循了LSP原则,因为Quadrilateral类和它的子类之间所共享的行为是完全一致的。在使用子类时,程序得到的结果与使用父类时是一样的。
4、总结
LSP原则:子类应该能够完全替代父类,并且拥有父类所有的行为和属性。
优点:
提高了代码的复用性。由于子类可以完全替代父类,所以可以通过继承来实现代码的共享和复用。
增加了代码的可扩展性。如果需要添加新的功能或修改现有功能,可以通过创建新的子类或重写父类的方法来实现,而不必修改原有的代码。
方便了代码的维护。由于子类和父类之间具有一致性,所以在修复父类代码时,也会自动修复所有的子类。
降低了代码的耦合性。由于子类依赖于父类,而不是具体的实现,所以在修改父类时,不必担心会影响到其他的子类。
缺点:
可能导致代码过度设计。为了满足LSP原则,可能需要创建大量的抽象类和接口,这可能会导致代码过于复杂和难以理解。
可能导致性能下降。由于过多的抽象层次和接口,可能会导致程序的运行效率降低。
可能会出现类型转换问题。在某些情况下,子类可能无法完全替代父类,需要进行类型转换,这可能会导致运行时错误。