Nic Lin's Blog

喜歡在地上打滾的 Rails Developer

以 OOP 的角度提升 Ruby code 質量

在 OOP 的角度下,兩個最基本的議題

  1. Cohesion 內聚
  2. Coupling 耦合
  • Cohesion 內聚: 指把功能需要用到的程式、資料都放在一個模組(function / class / package / library),該模組是一個單獨的個體執行
  • Coupling 耦合: 模組之間互相依賴,例如 A 模組必須依賴 B 模組傳進來的參數,否則 A 無法單獨運行,稱之為耦合

基本上軟體工程常言道:「高內聚,低耦合」的理想目標,這其中的定義拿捏沒有可以直接量化的標準,通常也依團隊開發習慣為準。

超高內聚零耦合擁有獨立性,可以單獨被使用單獨被修改,但如果一萬行呢?內聚力超高的 Code 我想難讀也難維護,更別說寫測試了。

所以要有高內聚,但又不能太高,這時候要就有耦合來平衡這種情況了,畢竟軟體開發總難以有每個模組之間互不相關的情況發生吧,所以這時候就會有比較常見的 Design Pattern 可以參考使用。

不過在套用 Design Pattern 之前,可以先檢視自己的 code 基本上有沒有低內聚或高耦合的情況發生,基本上都可以用簡單的解法先解掉。

內聚

不好的內聚就是把很多東西都放在同一個 class,就算 method 是獨立運作的,也因為上下文不相關導致難以查找或維護,舉個例子

class Bank
  def withdrawal
  end
  
  def deposit
  end
  
  def make_coffee
  end
end

這邊可以看到 make_coffee 在 Bank 銀行 class 裡面顯的有點多餘,就算這家銀行有提供咖啡的服務好了,那做咖啡這件事情其實也不屬於銀行的責任

  1. 做咖啡不需要知道銀行的操作(例如帳戶還有多少錢?是否足夠轉帳之類的)
  2. 銀行也不需要知道剩下多少咖啡或是如何製作咖啡

基本上 Class 裡面的 Cohesion 應該是要 share 一樣的 context,也就是上下文是有關連的

否則其實我們會花更多時間去瞭解

  • 這個 method 到底用到哪些資料?
  • 這個 method 實際到底做了什麼?
  • 和哪些 object 有關係?

以上述的例子來講,要解決這類型的問題,就是把無關上下文的部分提取出去,再做一個單獨的 class,例如

銀行有轉帳和存錢的方法

class Bank
  def withdrawal
  end
  
  def deposit
  end
end

飲料店有製作咖啡的方法

class BeverageShop
  def make_coffee
  end
end

結論:內聚應該要 share 一樣的 context

耦合

class ShoppingCart
  attr_accessor :items
  def initialize
    @items = []
  end
end

class Order
  def process_order(cart)
    cart.items.map(&:price).inject(:+)
  end
end

Order 已經對 ShoppingCart 有很高的耦合程度,因為他要知道的東西太多了,還要知道 cart 裡面的 item,也要知道裡面有 price 可以計算。

如果我們要修改 ShoppingCart 的實作,勢必也會影響到 Order,假設我們要把 price 換成 amount,那麼就要同時改兩個地方,這就是耦合。

我們可以降低耦合的部分

class ShoppingCart
  attr_accessor :items
  def initialize
    @items = []
  end
  
  def calculate_total
    items.map(&:price).inject(:+)
  end
end

class Order
  def process_order(cart)
    cart.calculate_total
  end
end

這樣一來 Order 只要知道可以 call calculate_total 這個 method 就好,他不需要瞭解太多關餘 ShoppingCart 的細節。

而當 ShoppingCart 修改 calculate_total 時,只要傳回的內容與原先一致,就是降低耦合了。

因為這時候我們只需要對一邊進行更改,而不會影響另一邊時,就不是高耦合了。

小結

  • 內聚的部分請 share 一樣的 context,如果不相關,請放到另一個 class
  • 降低耦合的作法是不要有太多的 dependency,維持最小知識原則(Law of Demeter)也許有幫助

參考資源

comments powered by Disqus