里氏替换原则 Liskov Substitution Principle

今天来谈谈 SOLID 当中的里氏替换原则,同样的先来看一下例子。

延续先前的例子,公司持续拓展,满足更多不同使用者的需求。现在公司决定,让使用者可以在建立实例之後,能够透过一些方法来调整形状的属性。以 Rectangle 来说,新增了 setWidthsetLength 方法分别调整宽和长。

class Rectangle implements Shape {
  width: number
  length: number

  constructor(width: number, length: number) {
    this.width = width
    this.length = length
  }

  setWidth(input: number): void {
    this.width = input
  }

  setLength(input: number): void {
    this.length = input
  }

  getArea(): number {
    return this.width * this.length
  }
}

所以如果原本建立 rectangle 的时候,设定长和宽为 7 和 7,之後我们可以改为长 11 宽 13,最後会得到面积 143

const rectangle = new Rectangle(7, 7)
rectangle.getArea()                     // 49
rectangle.setLength(11)
rectangle.setWidth(13)
rectangle.getArea()                    // 143

这时候部门里面有人看到上面的例子之後,有个突发奇想,想要让使用者可以同时修改长和宽。於是就建立了一个继承 Rectangle 的新类别 NewRectangle。而因为要能够实现刚刚的想法,所以也调整了原本 setLengthsetWidth 的实作方式

class NewRectangle extends Rectangle {
  constructor(size: number) {
    super(size, size)
  }

  setWidth(input: number): void {
    this.width = this.length = input
  }

  setLength(input: number): void {
    this.width = this.length = input
  }
}

结果的确能够顺利实作出这个新的类别,并且同样能够计算出面积。

const newRectangle = new NewRectangle(7)
newRectangle.getArea()                       // 49

这时客户看到有新的类别,就跃跃欲试的使用,当设定完长 11 宽 13 之後,本来以为会跟 Rectangle 一样,得到面积 143,但没想到这时候却得到结果 169

newRectangle.setLength(11)
newRectangle.setWidth(13)
newRectangle.getArea()                       // 169

客户没有得到期待中的结果,就找公司抱怨说:「为什麽用了你们家新版本的产品,却没有得到原本的结果?」

於是公司就回头跟各部门说:「你们要怎麽更新版本或是改变实作方式都没关系,但是满足客户同样的期待」

里氏替换原则

於是,公司内部就出现了一条新规则。

Substitutability is a principle in object-oriented programming stating that, 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.

在物件导向程序设计当中,里氏替换原则指出,如果 S 是 T 的子类别,,那麽 S 就需要能够取代 T 而不影响其他方面的执行。以刚刚的例子来说,当NewRectangle 继承了 Rectangle,那麽就会期待使用者可以用 NewRectangle 来取代 Rectangle,并看到期待中的结果。

从使用者角度出发

先前提到的「单一功能原则」、「开放封闭原则」、「依赖反转原则」,谈的都是当「外在」需求变动的时候,程序「内部」可以用最少的力气去修正与扩充,以满足新的需求。

但随着时间演进,程序内部可能会有新的实作方式、新的版本出现,但不管内部怎麽调整,对外部的使用者来说,应该都要维持同样的期待。如果无法维持同样的期待,那麽我们就需要花额外的力气去通知使用者调整使用方法或期待。


<<:  Day 14 : 笔记篇 01 — 了解 Obsidian 的 Metadata,建立一套可持续迭代的笔记系统

>>:  Day 14 : 案例分享(4.3) 签核与费用模组 - 签核关卡及条件设定

Day 18 wait group 的使用

Wait group wait group 通常用来等待一组 goroutine 完成工作。 wai...

CMM和CMMI

-CMM和CMMI成熟度级别比较 软件工程学院(SEI),1984年 软件工程学院(SEI)於19...

【PHP Telegram Bot】Day04 - Telegram 机器人的设定

今天要来设定机器人~ 按下 /mybots 指令後就会出现机器人列表 Choose a bot f...

Day6 Let's ODOO: Model(3) Decorators & Environment

依照昨天范例我们继续写下去 # -*- coding: utf-8 -*- from odoo im...

Day-27 特集:测试驱动开发 TDD

所谓测试驱动开发(Test-driven development, TDD),即「先写测试再开发」,...