在上一篇文章当中,我们提到「单一功能原则」,指每一个类别只会因为一种原因被修改。那麽,如果真的遇到需求变动、需要修改的时候,我们该如何「修改」呢?
SOLID 当中的开放封闭原则 Open-Closed Principle 提供了修改的原则:
In object-oriented programming, the open–closed principle states "software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification"; that is, such an entity can allow its behaviour to be extended without modifying its source code.
在物件导向程序设计当中,开放封闭原则指的是,在软件当中的实体 (不管是 classes, modules, or functions) 应该对於「扩展」是开放的,但对於「修改」是封闭的
好吧,刚刚说到要「修改」,但是这里又说要对「修改」封闭,到底是什麽意思呢?
先让我们来看看下面的例子吧
假设今天我们成立一间公司,专门负责计算形状面积,像是
const calculateArea = (object) => {
return xxx
}
当第一个需求上门的时,我们希望可以处理长方形的面积。因此我们建立了长方形的类别
class Rectangle {
width: number
length: number
constructor(width: number, length: number) {
this.width = width
this.length = length
}
}
接着,更新 calculateArea
方法:
const calculateArea = (object) => {
return object.width * object.length
}
最後成功计算出面积
const rectangle = new Rectangle(13, 17)
calculateArea(rectangle) // 221
当公司业务蒸蒸日上,突然有个新客户出现,希望我们也能够计算三角形的面积,这时候我们该怎麽办呢?
好吧,那麽我们就让 calculateArea
方法可以根据不同需求来计算面积。因此首先先调整原本的 Rectangle
类别,以及建立新的 Triangle
类别
class Rectangle {
shape: string
width: number
length: number
constructor(width: number, length: number) {
this.width = width
this.length = length
this.shape = 'rectangle'
}
}
class Triangle {
shape: string
base: number
height: number
constructor(base: number, height: number) {
this.base = base
this.height = height
this.shape = 'triangle'
}
}
接着让我们更新 calculateArea
,让他能够根据不同的形状,回传正确的面积(如果不是认识的形状,回传 null)
const calculateArea = (object) => {
switch (object.shape) {
case 'rectangle':
return object.width * object.length
case 'triangle':
return object.base * object.height / 2
default:
return null
}
}
之後就能够顺利得到结果
const a = new Rectangle(13, 17)
const b = new Triangle(11, 19)
calculateArea(a) // 221
calculateArea(b) // 104.5
後来公司生意越做越大,也开始有更多以前没有看过的需求进来。每一次有新的需求(形状)出现,我们就要去修改 calculateArea
方法。另外,在增加新的计算方法的时候,很有可能就不小心碰到原本的方法,进而造成错误发生。
这时候有没有什麽方法可以让事情变得简单一点呢?如果我们不要自己为各种形状建立计算面积的方法,而是让他们(形状)自己来告诉我们呢?
让我们来重新整理一下程序码。首先,建立一个叫做 Shape
的介面,他要求所有执行这个介面的类别,都需要有一个 getArea
的方法
interface Shape {
getArea(): number
}
接着,让原本的 Rectangle
和 Triangle
都执行 Shape
介面,并定义好 getArea
,也就是计算面积的方法
class Rectangle implements Shape {
width: number
length: number
constructor(width: number, length: number) {
this.width = width
this.length = length
}
getArea() {
return this.width * this.length
}
}
class Triangle implements Shape {
base: number
height: number
constructor(base: number, height: number) {
this.base = base
this.height = height
}
getArea() {
return this.base * this.height / 2
}
}
接着,calculateArea
方法可以简化成
const calculateArea = (object) => {
return object.getArea()
}
最後可以得到同样的结果
const a = new Rectangle(13, 17)
const b = new Triangle(11, 19)
calculateArea(a) // 221
calculateArea(b) // 104.5
在这样的情况下,不管未来有什麽奇怪的形状出现,我们只要建立新的类别,执行 Shape
介面,不需要更动 calculateArea
方法了!
从上面的例子来看,我们在做的事情就是,当新类别出现的时候
Shape
介面calculateArea
的修改虽然这里的 calculateArea
只是一个独立的方法,但他也可以是某个类别当中的一个方法。也就是说,当需求变动的时候,我们不需要进入这个类别来修改方法。
如果能实现「开放封闭原则」,就代表程序码具备扩展性和稳定性,不会因为需求而持续修改程序码,也让程序码更容易维护。
当然在真实世界复杂的情况下,可能无法做到纯粹的不做任何修改,但如果能够尽可能降低修改的发生,那麽就能够降低错误发生的可能性。
On the way from an engineer to transfer to a Busin...
Email 安全 为什麽要收集各个企业的 Email 信箱,透过了解企业公开在外的 Email,可...
ToggleButton为开关按钮,也就是说点一下就显示开启再点一下就显示关闭。 ToggleBut...
28 - Find the missing letter Don't say so much, ju...
=x= 🌵 WYSIWYG HTML Editor 所见即所得文字网页文字编辑器使用方式。 WYSI...