法则4:Liskov替换法则(LSP)
使用指向基类(超类)的引用的函数,必须能够在不知道具体派生类(子类)对象类型的情况下使用它们。
[ Function That Use References To Base(Super) Classes Must Be Able To Use Objects
Of Derived(Sub) Classes Without Knowing It ]
-
Liskov替换法则
1.显而易见,Liskov替换法则(LSP)是根据我所熟知的"多态"而得出的。
2.例如:
3.若一个函数未能满足LSP,那么可能是因为它显式地引用了超类的一些或所有子类。这样的函数也违背了OCP,因为当我们创建一个新的子类时,会不得不进行代码的修改。public void drawShape(Shape s) {
方法drawShape应该可与Sharp超类的任何子类一起工作(或者,若Sharp为Java接口,则该方法可与任何实现了Sharp接口的类一起工作)
// Code here.
}
但是当我们在实现子类时必须要谨慎对待,以确保我们不会无意中违背了LSP。
-
LSP示例
1. 考虑下面Rectangle类:// A very nice Rectangle class.
public class Rectangle {
private double width;
private double height;
public Rectangle(double w, double h) {
width = w;
height = h;
}
public double getWidth() {return width;}
public double getHeight() {return height;}
public void setWidth(double w) {width = w;}
public void setHeight(double h) {height = h;}
public double area() {return (width * height);
}
2.现在,Square类会如何呢?显然,一个正方形是一个四边形,因此Square类应该从Rectangle类派生而来,对否?让我们看一看!3.观察可得:
a.正方形不需要将高和宽都作为属性,但是总之它将继承自Rectangle。因此,每一个Square对象会浪费一点内存,但这并不是一个主要问题。
b.继承而来的setWidth()和setHeight()方法对于Square而言并非真正地适合,因为一个正方形的高和宽是相同。因此我们将需要重 写setWidth()和setHeight()方法。不得不重写这些简单的方法有可能是一种不恰当的继承使用方式。Square类如下:
// A Square class.
public class Square extends Rectangle {
public Square(double s) {super(s, s);}
public void setWidth(double w) {
super.setWidth(w);
super.setHeight(w);
}
public void setHeight(double h) {
super.setHeight(h);
super.setWidth(h);
}
}
public class TestRectangle {
// Define a method that takes a Rectangle reference.
public static void testLSP(Rectangle r) {
r.setWidth(4.0);
r.setHeight(5.0);
System.out.println("Width is 4.0 and Height is 5.0" + ", so Area is " + r.area());
if (r.area() == 20.0)
System.out.println("Looking good!/n");
else
System.out.println("Huh?? What kind of rectangle is this??/n");
}
public static void main(String args[]) {
//Create a Rectangle and a Square
Rectangle r = new Rectangle(1.0, 1.0);
Square s = new Square(1.0);
// Now call the method above. According to the
// LSP, it should work for either Rectangles or
// Squares. Does it??
testLSP(r);
testLSP(s);
}
}
Width is 4.0 and Height is 5.0, so Area is 20.06.看上去好像我们违背了LSP!
Looking good!
Width is 4.0 and Height is 5.0, so Area is 25.0
Huh?? What kind of rectangle is this??
7.这里的问题出在哪里呢?编写testLsp()方法的程序员做了一个合理的假设,即改变Rectangle的宽而保持它的高不变。
11. 一个数学意义上的正方形可能是一个四边形,但是一个Square对象不是一个Rectangle对象,因为一个Square对象的行为与一个Rectangle对象的行为是不一致的!
8.在将一个Square对象传递给这样一个方法时产生了问题,显然是违背了LSP
9.Square和Rectangle类是相互一致和合法的。尽管程序员对基类作了合理的假设,但其所编写的方法仍然会导致设计模型的失败。
10.不能孤立地去看待解决方案,必须根据设计用户所做的合理假设来看待它们。
12.从行为上来说,一个Square不是一个Rectangle!一个Square对象与一个Rectangle对象之间不具有多态的特征。
总结
2.为了保持LSP(并与开放-封闭法则一起),所有子类必须符合使用基类的client所期望的行为。
3.一个子类型不得具有比基类型(base type)更多的限制,可能这对于基类型来说是合法的,但是可能会因为违背子类型的其中一个额外限制,从而违背了LSP!
4.LSP保证一个子类总是能够被用在其基类可以出现的地方!
本文探讨了Liskov替换法则(LSP)的重要性,并通过Rectangle和Square类的实例,展示了如何确保子类能够正确替换其基类而不破坏程序的正确性。
726

被折叠的 条评论
为什么被折叠?



