面向对象编程的 SOLID 原则 - 里氏替换原则

发布时间 2023-07-29 16:52:50作者: 易先讯

里氏替换原则

里氏替换原则描述的是子类应该能替换为它的基类。

意思是,给定 class B 是 class A 的子类,在预期传入 class A 的对象的任何方法传入 class B 的对象,方法都不应该有异常。

这是一个预期的行为,因为继承假定子类继承了父类的一切。子类可以扩展行为但不会收窄。

因此,当 class 违背这一原则时,会导致一些难于发现的讨厌的 bug。

里氏替换原则容易理解但是很难在代码里发现。看一个例子:


class Rectangle {
	protected int width, height;

	public Rectangle() {
	}

	public Rectangle(int width, int height) {
		this.width = width;
		this.height = height;
	}

	public int getWidth() {
		return width;
	}

	public void setWidth(int width) {
		this.width = width;
	}

	public int getHeight() {
		return height;
	}

	public void setHeight(int height) {
		this.height = height;
	}

	public int getArea() {
		return width * height;
	}
}

有一个简单的 Rectangle class,以及一个 getArea 方法返回矩形的面积。

现在准备创建另一个 Squares class。众所周知,正方形只不过是宽和高相等的特殊的矩形。


class Square extends Rectangle {
	public Square() {}

	public Square(int size) {
		width = height = size;
	}

	@Override
	public void setWidth(int width) {
		super.setWidth(width);
		super.setHeight(width);
	}

	@Override
	public void setHeight(int height) {
		super.setHeight(height);
		super.setWidth(height);
	}
}

我们的 Square class 继承自 Rectangle class。在构造器里设置宽和高相等,我们不希望任何客户端(在他们的代码里使用我们的 class)违背了正方形的特性将宽高改成不相等。

因此我们重载了 setter 使宽和高任何一个改变时都会同时改变宽高。这样一来,我们就违背了里氏替换原则。

让我们先编写一个 test 来测试 getArea 函数。


class Test {

   static void getAreaTest(Rectangle r) {
      int width = r.getWidth();
      r.setHeight(10);
      System.out.println("Expected area of " + (width * 10) + ", got " + r.getArea());
   }

   public static void main(String[] args) {
      Rectangle rc = new Rectangle(2, 3);
      getAreaTest(rc);

      Rectangle sq = new Square();
      sq.setWidth(5);
      getAreaTest(sq);
   }
}

团队的测试人员提出测试函数 getAreaTest ,然后告诉你正方形对象的 getArea 函数不能通过测试。

在第一个测试中,我们创建了一个宽为 2 高为 3 的矩形,然后调用 getAreaTest,预期输出为 20,但是当传入一个正方形时出错了。这是因为调用测试里的 setHeight 函数会同时设置 width,导致输出结果不符预期。