开放封闭原则 Open-Closed Principle

在上一篇文章当中,我们提到「单一功能原则」,指每一个类别只会因为一种原因被修改。那麽,如果真的遇到需求变动、需要修改的时候,我们该如何「修改」呢?

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
}

接着,让原本的 RectangleTriangle 都执行 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 只是一个独立的方法,但他也可以是某个类别当中的一个方法。也就是说,当需求变动的时候,我们不需要进入这个类别来修改方法。

小结

如果能实现「开放封闭原则」,就代表程序码具备扩展性和稳定性,不会因为需求而持续修改程序码,也让程序码更容易维护。

当然在真实世界复杂的情况下,可能无法做到纯粹的不做任何修改,但如果能够尽可能降低修改的发生,那麽就能够降低错误发生的可能性。


<<:  Ruby on Rails 语言

>>:  Day19-不是恶魔 介绍DaemonSet

From Engineering to Business

On the way from an engineer to transfer to a Busin...

Day 7 被动搜查(4)-Email 相关、Harvester、Recon-ng

Email 安全 为什麽要收集各个企业的 Email 信箱,透过了解企业公开在外的 Email,可...

[Android Studio 30天自我挑战] ToggleButton元件介绍

ToggleButton为开关按钮,也就是说点一下就显示开启再点一下就显示关闭。 ToggleBut...

见习村28 - Find the missing letter

28 - Find the missing letter Don't say so much, ju...

Day 11 - Using CKEditor + CKFinder WYSIWYG Editor with ASP.NET Web Forms C# 在网页嵌入 WYSIWYG 文字编辑器

=x= 🌵 WYSIWYG HTML Editor 所见即所得文字网页文字编辑器使用方式。 WYSI...