30-4 之软件架构设计原则 3 - LSP 里氏替换原则

软件架构设计原则一切都是为了下面这两点,别忘了。

  • 低耦合
  • 高内聚

LSP 这个原则比较倾向是在物件导向才会有的设计原则,这也正常毕竟我们在讨论的软件设计原则 SOLID 最开始为『 物件导向设计原则 』,但是在很多地方事实上也通用,不过这个原则就比较算是在物件导向才能用的。

LSP ( Liskov-Substitution Principle ) 里氏替换原则是什麽呢 ?

根据 《 Clean Architecture 》这本书提到 1988 年,Barbara Liskov 写下定义子型态的方式 :

若对型态 S 的每一个物件 o1,都存在一个型态为 T 的件 o2,使得在所有针对 T 编写的程序 P 中,用 o1 替换 o2 後,程序 P 的行为功能不变,则 S 是 T 的子型态

我觉得比较白话文的说法为 :

在使用父类别的地方,如果替换成子类别,则行为功能不变

为什麽会要有这个准则呢 ? 你想想如果替换成子类别不能用,那是不是就有机率这个子类别实际上,不适合长在这个父类别下,因为子类别已经破坏了父类别的继承体系,就有可能会在未来发生可能的 Bug。

像我在实务上的确有看过,有个子类别已经违反 LSP,但是因为一些原因与时间很难改,结果导致父类别有很多地方,要为了这个子类别写特殊判断,结果特殊判断和原本的行为打架。

不过在 《 Clean Architecture 》 有提到一个重点 :

LSP 视为指导 『 继承 』的使用,但是多年来已涉及到介面与实作,它已经演变成更广泛的软件原则

也就是说这个原则,已经慢慢变成『 除了继承以外的准则 』

范例

这个范例是 《 Clean Architecture 》中最有名的正方形与长方形问题,正方形只是长方形长与宽相同的情况,所以这里范例正方形继承长方形。

然後这个范例违反 LSP 的情况为,如果是用长方形父类别来计算面积答案就对,而如果改成用正方形来计算面积就会错。别忘了 LSP 的定义为 :

在使用父类别的地方,如果替换成子类别,则行为功能不变

class Rectangle{
    h:number
    w:number
    setHeight(h): void{}
    setWidth(w): void{}
    getArea(){
        return this.w * this.h
    }
}

class Square extends Rectangle{
    setHeight(h): void {
        this.h = h
        this.w = h
    }
    setWidth(w): void {
        this.h = w
        this.w = w
    }
}

const square = new Square()
square.setHeight(10)
square.setWidth(20)

// 这里就会有问题,行为和答案不合
const area = square.getArea()

小总结

LSP 目前应该是我实务上比较少碰到的,比较大的原因在於比较少用到继承,主要的原因在於继承是个依赖性很高的东西,而果真的要用到继承我也会倾向以『 做什麽 』来设计这个父类别。详细为什麽不用的原因我觉得这篇文章写的很清楚了 ~ 可参考

老样子,来问一下三个问题,来加深记忆

这个知识点可以用来解释什麽现象

LSP 原则基本上是在讨论什麽情况使用继承会有问题,而事实上很多在研究这个原则时,也慢慢的发现使用继承的问题。

这个知识点可以和以前的什麽知识连结呢 ?

LSP 让我连想到以前在讨论继承的可能问题,例如 :

  • 继承可以减少不少程序码,但同时也有负牵一发动全身的问题
  • 容易让人在父类别加方法,但确发现只有一个子类别用到
  • 继承有时可能会导致维护性下降,例如看一段子类别有个 workaround,然後往上追可能要到祖父类别後,才知道这个 workaround 是为了解决祖父类别给其它子类别的 workaround 的 workaround。

我要如何运用这个知识点 ?

  • 在开发时倾向用组合或 interface 来处理
  • 如果真的要使用继承多思考在不同的子类别在使用上会不会冲突

参考资料


<<:  Day 19 - Unreal Webcam Fun [更新]

>>:  [DAY 04] EC2 Security Group

Day-08 比训练更重要的事情,Dataset

昨天我们提过我们的目标是成功分类一组资料,那...资料哪来啊 OAO?总不能每次我要练习之前,还要...

Day13. class_eval & instance_eval - 解答什麽是 MetaClass & Singleton

接下来介绍的章节,会使用到instance_eval, class_eval,加上我们已经在 Day...

网路是怎样连接的(七)TCP的交互(下)

思考重点 TCP如何确认对方收到消息? 讯息收发中的头部消息变化? 关闭连接操作? 核心知识 封包的...

【Day 24】上百种 Provider 任意选,这样的 ETW 你喜欢吗 - ETW 监控 Process

环境 Windows 10 21H1 Visual Studio 2019 前情提要 在【Day 2...

网路是怎样连接的(三)浏览器与HTTP

思考重点 网页浏览器怎麽获取网站消息 当我们输入网时会发生什麽事 常见的404 not found意...