面向对象设计原则 ---- 里氏替换原则(LSP)
Liskov替换: OCP的关键:抽象。抽象的威力在于多态和继承。
正确的继承-------符合里氏替换原则。
简言:子类型必须能够替换掉他们的基类性。
里氏替换原则的核心:
不是静态分析两个对象之间知否有继承关系,而是在应用场景,验证子类能不能替换父类。
如果替换后,能正常运行=>合理的继承。
如果不能正常运行=>不合理的继承。
静态视角:垂直层面,使用抽象向下扩展子类----------------------IS-A静态关系。
动态视角:水平层面,放到应用环境上下文,验证继承的正确性。
举例:验证白马,黑马,小马的继承正确性。
应用上下文:人骑马-----------将马的任意对象,来替换到应用场景。
人骑白马---------正确
人骑黑马---------正确
人骑小马---------错误------不合适的继承。
在使用Horse对象的任何场合,我们可以把WhiteHorse对象传递进去,以取代Horse对象,程序仍然正确。
验证方法:在应用场景中,使用子类替换父类,验证程序能不能正常运行。
案例二:评估是否符里氏替换原则?
void drawShape(Shape shape){
if(shape instanceof Circle){ drawCircle((Circle)shape);}
else if(shape instanceof Square){drawSquare((Square)shape);}
else { .... }
}
评估方法:将Shape的任意子类,传递进去,程序都能正常运行。
验证1:传递子类Circle(实现Shape)====运行正常====正确。
验证2:传递子类Square(实现Shape) ====运行正常====正确。
验证3:传递子类三角形 (实现Shape)====无法正常处理===违反LSP。
改进方法:
void drawShape(Shape shape){ shape.draw();}
案例3:“正方形” is-a “长方形”吗?
public class Rectangle{
private double width;
private double height;
public void setWidth(double w){this.width=w;}
public double getWidth(){return this.width;}
public double getHeight(){return this.height;}
public void setHeight(double h){this.height=h;}
}
正方形可以继承正方形吗?
public class Square extends Rectangle{
public void setWidth(double w){this.width=height=w;}
public void setHeight(double h){this.height=width=h;}
}
假如测试方法:
void testArea(Rectangle rect){ //动态分析:传入正方形,断言失败。
rect.setWidth(3);
rect.setHeight(4);
assert(12==rect.calculateArea());//传入正方形将失败。
}
解析:静态分析,Square继承Rectangle没有问题。
动态分析(里氏替换分析应用场景):
1.传入长方形,断言成功,面积计算正确 ,
2.传入正方形,断言失败,面试计算错误
===>正方形不是长方形。
===> Square extends Rectangle就是不合理的继承。
LSP要求:凡是使用基类的地方,一定也适用于其子类。
Java语法角度看:
子类一定得拥有基类的整个接口。
子类的访问控制不能比基类更严格。
子类的“契约"不能比基类更”严格“。
评论