2 设计模式原则之里约替换原则
一、里约替换原则
1.定义
任何基类可以出现的地方,子类一定可以出现。
通俗理解:子类可以扩展父类的功能,但不能改变父类原有的功能。
换句话说,子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。
2.具体用法
如果通过重写父类的方法来完成新的功能,这样写起来虽然简单,但是整个继承体系的可复用性会比较差,特别是运用多态比较频繁时,程序运行出错的概率会非常大。
3.代码举例
public class RectangleDemo {
public static void main(String[] args) {
//创建长方形对象
Rectangle rectangle = new Rectangle();
//设置长和宽
rectangle.setWidth(10);
rectangle.setLength(20);
//扩宽与打印
resize(rectangle);
printLengthAndWidth(rectangle);
System.out.println("======================");
//创建正方形对象
Square square = new Square();
//设置长和宽
square.setLength(10);
//扩宽操作
resize(square);
printLengthAndWidth(square);
}
//扩宽方法
public static void resize(Rectangle rectangle){
//判断宽如果比长小,则扩宽
while(rectangle.getWidth() <= rectangle.getLength()){
rectangle.setWidth(rectangle.getWidth() + 1);
}
}
public static void printLengthAndWidth(Rectangle rectangle){
System.out.println(rectangle.getLength());
System.out.println(rectangle.getWidth());
}
}
public class Rectangle {
private double length;
private double width;
public double getWidth() {
return width;
}
public void setWidth(double width) {
this.width = width;
}
public double getLength() {
return length;
}
public void setLength(double length) {
this.length = length;
}
}
public class Square extends Rectangle{
@Override
public void setLength(double length) {
super.setLength(length);
super.setWidth(length);
}
@Override
public void setWidth(double width) {
super.setLength(width);
super.setWidth(width);
}
}
4.案例分析
里氏替换原则的破坏点
- 测试用例中,
resize
方法接受的是一个Rectangle
对象,它的逻辑是 "如果宽度小于等于长度,就不断地扩宽"。 - 当传入
Rectangle
对象时,这个逻辑是没问题的,最后宽度比长度大,符合预期。 - 但是,当传入的是
Square
对象(正方形),因为Square
重写了setLength
和setWidth
方法,使得length
和width
始终相等,导致在resize
方法中的循环无法达到 "宽度大于长度" 的状态。
这就违背了里氏替换原则:
- Rectangle 和 Square 是父子类关系,但是 Square 不能替代 Rectangle 使用,破坏了父类原有的行为("长方形的长和宽可以独立变化")。
- 也就是说,
resize
方法设计时假设传入的对象是标准的长方形,length
和width
是独立的,而Square
强制让这两个值相等,破坏了这种假设。
解决方案:
- 不应该让
Square
继承Rectangle
,因为它们在概念上并不是 “is-a” 的关系,而是 “has-a” 的关系(正方形是特殊的四边形,不是长方形的子类)。 - 可以重新设计类结构,让
Rectangle
和Square
独立存在于同一个平级层级,继承一个共同的抽象父类,如Shape
。
5.总结
- 里氏替换原则 强调子类在继承父类时,不应破坏父类原有的功能和语义。子类必须能替代父类在程序中的使用,且程序功能不会受到影响。
- 在例子中,
Square
强制让length
和width
相等,破坏了Rectangle
的语义,导致无法替代Rectangle
使用,违反了里氏替换原则。 - 改进方法:重新设计类结构,使
Square
和Rectangle
继承同一个共同的父类(如Shape
),而不是相互继承。