Open–closed principle (OCP) 開放封閉原則

一個軟體應該是對於擴展開放、但對於修改封閉的

Huashen
Apr 9, 2021

什麼是OCP

開放封閉原則,簡稱OCP(Open–Closed Principle),核心思想是在不修改原有程式碼的情況下,能夠擴展及修改自己的功能

大家常見的Chrome瀏覽器,以及我們所使用的VS Code文字編輯器,都有一個相似的功能,那就是”擴充套件”。Chrome和VS Code為何允許我們安裝各種套件,甚至是自己製作的套件,而不怕我們破壞原有的功能呢?就是因為這些軟體提供了一個透過OCP隔離的環境,讓我們有辦法在原有的核心功能之上擴展自定義的行為,而不用修改到原有的程式碼,避免改壞掉的風險。

為什麼要有OCP

寫程式最怕的不是想程式碼,而是改程式碼。對於一個高度耦合的程式來說,想要在幾百甚至幾千行的程式碼中找到要修改的地方,是非常困難的,一不小心就可能更動到了其他的功能,而讓原本可以正常執行的程式掛掉,又要回過頭來花上一段時間檢查是哪裡出問題。

如果程式有遵循OCP原則,那我們在修改或新增功能時,就不會影響到其他已經做好的功能,將可以大大減少我們的開發時間

如何做到OCP

有兩種方法可以做到做到OCP:

  1. 繼承 : 透過用新的類別繼承舊有類別,保有原有功能的情況下又能新增新功能在新的類別上,滿足對擴展開放、對修改封閉。
  2. 介面 : 透過將主要功能介面化(抽象化),讓功能相依於介面而不是實作,這樣我們只要擴展實作的類別,就可以新增新功能,滿足對擴展開放、對修改封閉。

依賴介面的方式在許多設計模式中都可以看到,而這也是DIP依賴反向原則的核心概念。因此在這裡主要示範依賴繼承的方式。

我們從我們的上一個專案來做修改,首先複製一份原先的專案,我們這次我們這次把資料夾命名為OCP,然後也把src裡的index.ts修改為以下:

import { GoodGame } from './OCP/Good';new GoodGame();

接下來擴展我們的Deck功能,我們的手牌需要排序,但目前的Deck並沒有這個功能,我們也不能直接修改Deck本身的程式碼,因為這樣就違反了OCP原則(另一個原因是若Deck提供排序功能,而不知情的人呼叫了,那我們的洗牌就白費了),因此,我們要擴展功能的其中一個途徑就是利用繼承:

我們創建了新的HandDeck.ts檔案以及HandDeck類別,並讓HandDeck繼承Deck類別,這樣我們在提供HandDeck類別排序功能的時候,也不會影響到原本Deck的運作。(需要注意原先Deck類別的deck屬性須改為protected)

protected deck: Card[] = [];

接著將我們的Good.ts的進行修改,引入HandDeck類別:

接著執行我們的index.ts,忘記我們的指令可以回package.json看,這裡再複習一次(記得分開在不同的terminal執行):

yarn watchyarn start

可以看到我們排序過後的手牌被成功的印出來了,但是採用的是JavaScript預設的排序方式,因此我們對排序的方法做一點修改,首先修改Card.ts:

我們新增toValue的方法,讓我們可以外部存取Card的value,並且修改之前定義的花色,讓花色也可以按照大小排列,接著修改sort的實作:

this.deck.sort((card1, card2) => card1.toValue() - card2.toValue());

這樣修改後可以看到手牌以我們想要的排序排列了。

總結一下

經過這次的修改,我們用新的HandDeck類別來簡單的擴充了Deck類別,使他擁有sort的方法,並且不會影響我們之前的所有功能,這就是OCP所帶來的好處。但實際上,這樣利用繼承來擴充的作法會有一些缺點,在下一篇關於LSP 里氏替換原則 的文章中我們就會看到。

下一篇文章<Liskov substitution principle 里氏替換原則>

如果對於我的文章或程式碼有任何問題,歡迎在下方留言指教。

若有幫助到你,也歡迎給文章拍手一下,讓我在寫文章的路上更加進步!

--

--

Huashen

嗨,我是Huashen,一位軟體工程師,這裡會記錄我的程式設計心得與筆記。