均为原创,读架构整洁之道的笔记。
里氏替换原则:(LSP :Liskov Substitution Principle)。
使用一个父类对象,替换成该父类对象的子类对象后,该程序不会发生异常。(该书指的为接口,而没有提到继承关系)
或者说,调用一个Interface
,切换成直接调用该接口的实现对象后,该程序不会发生异常。(该书中的表述为接口)
继承的使用指导
场景
Billing
调用License
,获取授权费用。License
接口有两个实现类。这两个实现类中计算授权费用的规则不一样,但是业务是一样的。按照业务切换实现类,业务是可以正常进行的,不会破坏程序的正确性,不会发生异常。最关键的是这两个实现类,可以直接替换掉License
接口。这样就是符合LSP
原则的。
正方形/长方形问题
以下设计中,正方形/长方形问题,是一个著名的违反LSP
的设计案例。
可以看到,User
调用Rectangle
为获得长方形的面积,可设置宽高。而正方形与长方形的设置宽高的逻辑并不一致,所以正方形错误的继承了Rectangle
。如果使用Square
,来替换掉Rectangle
,就会发现错误。看下面的例子。
Rectangle r = ... //当这里是长方形时Rectangle,这个断言自然能通过。但是
//如果切换成正方形Square时,这个断言是通过不了的。
r.setW(5);
r.setH(2);
assert(r.area()==10);
即可以确认,子类型,并不能完全替代其父类,会发生逻辑上的问题。所以这是一个违反LSP
的案例,正方形不该继承于或者说成为长方形的子类型。
要想防范这种LSP
的行为,唯一的办法就是在User类中增加用于区分Rectangle
和Square
的检测逻辑,如if语句
,但是这就造成了强依赖,使用者强烈依赖被使用者。
LSP 与软件架构
可以看出,上述讲的是类和接口的继承与实现关系。然而随着时间推移,LSP
演变成了一种更广泛的,指导接口与其实现方式的设计原则。
违反 LSP 的案例
书中举的是一个出租车调度服务程序,面向多个出租车公司,用户发出请求,由调度服务运算选择出租车公司中的某一辆车,但是其中有一家公司的调用方式,和其他公司不一样,所以很难做切换和统一。
面对系统要向多个第三方提供服务,接口设计应当在调用方式和存储上,统一格式,并能严格区分和识别流量。使其在切换调用第三方时,代码不用做改动。这里将第三方看作是可替换的组件。
本章小结
LSP
可以且应该被用于软件架构层面,因为一旦违反了可替换性,该系统就不得不为此增添复杂的应对机制。