SOLID之LSP

里氏代换原则 LSP,Liskov Substitution Principle

子类型必须能够替换掉它们的基类型

若对每个类型S的对象O1,都存在一个类型T的对象O2,使得在所有针对T编写的程序P中,用O1替换O2后,程序P行为功能不变,则S是T的子类型

LSP是继承关系设计基本原则,也是使OCP成为可能的主要原则之一。正是子类型的可替换性才使得使用基类类型的模块在无需修改的情况下就可以扩展,对于LSP的违反常常会导致以明显违反OCP的方式使用运行时类型辨别(RTTI),这种方式常常是使用一个显式的if语句或才if/else链去确定一个对象的类型

假设一个函数f,它的参数为指向某个基类B的指针或者引用。同样假设B的某个派生类D,如果把D对象作为B类型传递给f,会导致f出现错误的行为。那么D就违反了LSP。显然,D对于f来说是脆弱的。

f的编写者会想去对D进行一些测试,以便于在把D的对象传递给f时,可以使f具有正确的行为。这个测试违反了OCP,因为此时f对于B的所有派生类都不再是封闭的

IS-A

“IS-A”是严格的分类学意义上的定义,意思是一个类是另一个类的“一种”

我们经常说继承是IS-A关系,也就是如果一个新类型的对象被认为和一个已有类的对象之间满足IS-A关系,那么这个新对象的类应该从这个已用对象的类派生

从一般意义上讲,一个正方形就是一个矩形。因此,把Sequare类视为从Rectangle类派生是合乎逻辑的

1
2
3
4
5
void f(Rectangle r) {
r.setWidth(5);
r.setHeight(4);
assert(r.Area() == 20)
}

此时,如果传入的是Sequare对象,那这个函数f不能正确处理,也就是Squauare不能替换Rectangle,也就违反了LSP,意味着LSP与通常的数学法则和生活常识有不可混淆的区别

在OOD中IS-A关系是就行为方式而言,而不是属性,这也就是面向接口编程;派生类的行为方式和输出不能违反基类已经确立的任何限制。基类的用户不应该被派生类的输出扰乱

简单判断就是“可替换性”,子类是否能替换父类并保持原有行为不变

LSP与架构

LSP从诞生开始,也就差不多这些内容,主要是指导如何使用继承关系的一种方法。随着时间推移,在更宏观上,LSP逐渐演变成了一种更广泛的、指导接口与其实现方式的设计原则

可以是java风格的接口,具有多个实现类:甚至可以是几个服务响应同一个rest接口,用户都依赖于一种接口,并且都期待实现该接口的类之间能具有可替换性

一旦违背了可替换性,该系统架构就不得不为此增添大量复杂的应对机制

公众号:码农戏码
欢迎关注微信公众号『码农戏码』