# 让里氏替换原则为你效力

## 面向对象的基石

SOLID由五大原则构成：

1. Single Responsibility Principle【单一职责原则】
2. Open Close Principle【开闭原则】
3. Liskov Substitution Principle【里氏替换原则】
4. Interface Segregation Principle【接口隔离原则】
5. Dependency Inversion Principle【依赖倒置原则】

## 打破里氏替换原则

In a computer program, if S is a subtype of T, then objects of type T may be replaced with objects of type S without altering any of the desirable properties of the program (correctness, task performed, etc.) -- Barbara Liskov in 1987

```class RectangleTest {
@Test
void should_return_area_when_calculate_given_width_and_height_valid() {
Rectangle rectangle = new Rectangle();
rectangle.setHeight(3);
rectangle.setWidth(5);

assertThat(rectangle.calculateArea()).isEqualTo(15);
}
@Test
void should_return_area_when_calculate_given_width_and_height_valid() {
Rectangle rectangle = new Square();
rectangle.setHeight(3);
rectangle.setWidth(5);

assertThat(rectangle.calculateArea()).isEqualTo(25);
}
}```

```public class Rectangle {
protected double width;
protected double height;
public void setWidth(double width) { this.width = width; }
public void setHeight(double height) { this.height = height; }
public double calculateArea() { return width * height; }
}

public class Square extends Rectangle {
@Override
public void setHeight(double height) {
this.height = height;
this.width = height;
}
@Override
public void setWidth(double width) {
this.height = width;
this.width = width;
}
}```

## 从复用来看继承

• 封装：隐藏对象的属性和实现细节，仅对外公开接口。比如`Rectangle`类，首先对自身属性`width``height`进行了隐藏，通过`calculateArea`方法提供服务，将依赖自身数据的计算细节也进行了隐藏
• 继承：允许子类在不需要重新编写父类的前提下，复用父类的所有功能，并能够按需进行扩展。比如`Square`继承了`Rectangle`，就具有了`calculateArea`的功能。（当心：这个继承不合理）
• 多态：允许对象在运行期表现出不同的形体

```public class Bird {
public void fly() {
System.out.println("I am flying");
}
}

public class Parrot extends Bird {}```

## 抽象是为了更好地复用

`Square`继承自`Rectangle``Square`能够复用`Rectangle`中的所有行为，假如你不对`Square`做任何事情就能完美复用，但这样子出来的正方形可能宽和高就不一样了（无法满足客户真实需求，这可都是无用功哟）。为了满足客户需求，你就不得不对`setWidth``setHeight`进行重写：

```public class Square extends Rectangle {
@Override
public void setHeight(double height) {
this.height = height;
this.width = height;
}
@Override
public void setWidth(double width) {
this.height = width;
this.width = width;
}
}```

```@Test
void should_return_area_when_calculate_given_width_and_height_valid() {
// Replace with Square
Rectangle rectangle = new Rectangle();
rectangle.setHeight(3);
rectangle.setWidth(5);

// 25 if using Square
assertThat(rectangle.calculateArea()).isEqualTo(15);
}```

`Square``Rectangle`都有宽和高，并且计算面积的方式一样，不同的是`setWidth``setHeight`。是否可以将共同的特征进一步抽象提炼。就这样逼着自己去思考，你可能很快就抽象出一个四边形，因为`setWidth``setHeight`行为不确定，先将它们抽象化。

```public abstract class Quads {
protected int width;
protected int height;

public abstract void setWidth(int width);
public abstract void setHeight(int height);

public int calculateArea() { return width * height; }
}```

```class QuadsTest {
@Test
void should_return_area_when_calculate_given_width_and_height_valid() {

}
@Test
void should_return_area_when_calculate_given_width_and_height_valid() {
}
}```

## 注释

• KiSS: Keep it simple, stupid
• DRY: Don't Repeat Yourself
• LKP: Least Knowledge Principle (LOD: Law of Demeter)
• CoC: Convention over Configuration
• DbC: Design by Contract
• SoC: Segregation of Concerns
• HP: Hollywood Principle

## 参考阅读

Posted by 袁慎建@ThoughtWorks

13 篇文章18 人订阅

0 条评论