iT邦幫忙

2021 iThome 鐵人賽

DAY 3
1
自我挑戰組

馬克的軟體架構小筆記系列 第 3

30-3 之軟體架構設計原則 2 - OCP 開放封閉原則

  • 分享至 

  • xImage
  •  

上一章節我們有簡單的在提一下軟體架構設計的兩個基本原則 :

  • 低耦合
  • 高內聚

這裡我們在提一次。然後接下來我們這篇文章將要來談談 SOLID 中的 OCP 開發一封閉原則,也是為了達成上面這兩件原則。

OCP ( Open-Closed Principle ) 開放封閉原則是什麼呢 ?

根據 《 Clean Architecture 》這本書裡所書寫的定義為 :

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

這個比較白話文的來說我覺得是 :

當有新功能時,是可以用類式 plugin 的方式加新功能,而不是去修改原本的程式

這個違反這個原則,事實上在我們寫的程式碼中非常的常見,你想想 ~ 是不是在程式中,有很多 function 裡會使用 if else 情境來判斷要用什麼來處理,例如訂單可能會根據不同產品來進行一些對同處理,這時我們是不是最直覺的寫法就是加個 if 呢 ?

我個人是認為在開發初起,我們很難預測未來的變動,所以這時簡單用個 if 來處理,就我個人是覺得可以接受,但是如果之後又來 ~ 那我就覺得真的可能考慮重構了呢。

這裡只要記好一件事情 :

開放封閉原則在軟體架構存在的目的,就是在於怕你要改時,會動到原本的程式碼,而導致可能的出錯

但我小聲的說…如果有寫測試,那事實上不太會影響…… 在某些小型專案上。

但是這個如果拉到大型專案上,一直用 if else 又或是一直在原本的地方加功能,會哭的不是你,而是後人,活生生的血淚屎。

範例

首先來來個基本版,就是一個專門用來處理訂單的類別,然後他有個方法可以計算這個訂單的費用。

// bad

class OrderFeeCalculator{
    constructor(order){
        this.order = order
    }

    calcFee(){
        let fee = 0
        fee = 0.1 * this.order.price
        return fee
    }
}

然後這時有個新需求,我們訂單的類型為『 文章 ARTICLE 』的話,則費用只要價格的 5% 就好,那這時通常第一直覺會直接改成如下 :

// Bad 2
class OrderFeeCalculator{
    constructor(order){
        this.order = order
    }

    calcFee(){
        let fee = 0
        if(this.order.category === 'ARTICLE'){
            fee = 0.05 * this.order.price
        }else{
            fee = 0.1 * this.order.price
        }
        return fee
    }
}

這一題事實上可以用一些設計模式來解決。

策略模式 + 簡單工廠解法

這樣如果某一個產品需要特殊的計算,那就只要在對應的 strategy 裡修改運算就 ok 囉,而不用怕會影響到其它產品的運算。

// Good
class OrderFeeCalculator{
    constructor(order){
        this.order = order
    }

    calcFee(){
        let fee = 0
        fee = _productFeeStrategyFactory[this.order.category].calcFee(this.order)
        return fee
    }

    _productOrderFeeFactor(category){
        switch (category) {
            case 'ARTICLE':
                return new ArticleFeeStrategy()
            case 'COURSE':
                return new CorseFeeStrategy()
        }

    }
}

class ArticleFeeStrategy{
    calcFee(order){
        return this.order.price * 0.05
    }
}
class CorseFeeStrategy{
    calcFee(order){
        return this.order.price * 0.1
    }
}

小總結

事實上我仔細想想,不少的設計模式都是為了來達到『 開放封閉原則 』,例如 :

  • 策略模式 : 如範例將每種產品的費用計算方法抽出,然後每當有新增產品時,只要新增策略就好,而不用修改原本的程式碼
  • 配接器模式 ( adapter pattern ) : 這個我在第三方串接或接老系統時很常用到,就是通常會包一層起來,將 interface 形狀用成符合我們內部用的, 這樣就不同單心第三方一個修改,全世界都要修改的問題
  • 觀查者模式 : 假設有個需求是訂單完成後,需要做某件事情 A,然後假設又有 B → Z 個事情要做,那你就會發現,這個 orderComplate 方法超長,而且應該依賴了不少其它 moduel 來處理 A → Z 的事情。事實上可以反過來,讓各地方監聽訂單完成事件,然後要處理什麼事情,由那個地方來處理,這樣是不是在新增需求時,不必修改 orderComplate 呢 ?

老樣子,來問一下三個問題,來加深記憶

這個知識點可以用來解釋什麼現象

我覺得分層架構也有一定原因是因為這個原則而產生的,我們不希望 Presenters 層級的修改,影響到 Controller。

這個知識點可以和以前的什麼知識連結呢 ?

分層架構、與一些設計模式都有連結,這些都是可以讓我們們更接近 OCP 的方法。

我要如何運用這個知識點 ?

  • 在開發任何單位的東西 ( function、class、module、layer ) 都需要思考未來是否有需求,會導致這個地方大量的修改,如果答案是,則可以考慮用一些設計模式或啥的來解決。

參考資料


上一篇
30-2 之軟體架構設計原則 1 - SRP 單一職責原則
下一篇
30-4 之軟體架構設計原則 3 - LSP 里氏替換原則
系列文
馬克的軟體架構小筆記29
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言