你在使用 OOP(物件導向程式設計)的全部威力嗎?還是錯過了什麼?
如果你根據物件的類型做決策,你就錯過了一項重要的 OOP 特性:多型(polymorphism)。
類型決策通常在 case 語句裡做(這不 OO 友善),在這篇文章你將學會如何透過移除它們寫出更好的代碼。
檢查類型
先展示一個沒利用多型的例子。
我們要實作「剪刀石頭布」遊戲,決定有一個 Game 類別和每個可能動作的一個類別。
為了檢查贏家,我們在 Game 實作 play 方法:
class Game def self.play(move1, move2) return :tie if move1 == move2 move1.wins_against?(move2) end end
這裡是其中一個動作(其他遵循相同模式):
class Rock def wins_against?(other_move) case other_move when Paper then false when Scissors then true end end end
現在我們可以用兩個動作呼叫 play 方法,就知道第一個動作是否獲勝。
p Game.play(Rock.new, Paper.new) # false
好,這能動,但能更好嗎?能移除那醜陋的 case 語句嗎?
用多型取代類型檢查
能!我們可以用 OOP 基礎移除類型檢查的 case 語句。
想法是利用我們知道當前類別,問另一個動作物件它能不能打敗我們。
我們將用一個專屬於我們類別的方法名(對 Rock 方法名可以是:do_you_beat_rock?)
在 Ruby 中,多型是指能發送任何方法呼叫(在 OOP 術語中也稱為訊息)給任何物件,而不必檢查物件的類別。這也稱為「duck typing」,但我不喜歡那個術語 🙂
在 Java(忍耐一下……),你有所謂的「interfaces」允許你在編譯層級強制一組方法由類別實作。
Ruby 沒有那個(可能比較好),所以你得靠測試。
來看新實作的程式碼範例:
class Rock def wins_against?(other_move) other_move.do_you_beat_rock? end def do_you_beat_paper? false end def do_you_beat_scissors? true end end
注意 case 語句不見了。它被單一個方法呼叫和兩個方法定義取代。
更新: 如一些讀者留言,我沒注意到邏輯在實作這個模式時反轉了。Daniel P. Clark 提出的解決方案是直接在
play方法反轉順序為move2.wins_against?(move1)。
這不乾淨多了?你覺得如何?
再加一個動作!
現在假設你想加新動作。你得改什麼?
想一分鐘……
用 case 語句方法,你得給每個動作加新分支。注意你被「迫」改一個完美運作的方法來引入新功能。
但用我們更 OOP 導向的版本沒這問題。我們要做的只是加新方法。這是開放/封閉原則(SOLID 的「O」)在行動。
另一個選項是用 metaprogramming(用 method_missing 或 define_method)。
我實際上會用 metaprogramming 嗎?
可能不會,除非動作數量持續變化,或有大量動作。
Metaprogramming 是有代價的,不是銀彈。你會用效能和可讀性換一點額外彈性。
結論
這篇文章你學到應該 避免用 case 語句檢查類別類型。相反,你應該利用 多型。
這將幫助你建立更好的代碼,可以透過加新代碼擴展,而不是改現有代碼。現在輪到你採取行動做重構 🙂
順帶一提這不代表我主張完全從工具箱移除 case 語句,但當你發現自己在寫 case 語句時要小心。確定那是解決問題的最佳方案。
如果你想要我繼續寫這類文章別忘了 分享這篇文章!