Ruby當中的class method和instance method差在哪?

Ruby新手剛開始都知道,class method就是在定義method時加上self,就變成class method。例如:

class Greeting
  def hello
    "hello"
  end

  def self.hello
    "hello"
  end
end

這樣不管我們執行Greeting.hello還是Greeting.new.hello都會有一樣的結果,而這兩種method到底有什麼差別呢?

可以想像一個class是一個大團體,裡頭的每一個人都是一個instance,假如我們要對整個團體下命令,就是在執行class method;反之假如是對其中一個人下命令,則就是執行instance method。

概念如下圖:

從Rails來理解

在學習管理Rails的model時,這樣子的概念比較好解釋,假如我需要針對個別的Post來檢視其去掉空白的內容長度,則可以進行以下設定。

class Post < ActiveRecord::Base
  def content_length
    self.content.tr(" ","").length
  end
end

# 實際使用
Post.first.content_length

像這樣的method,我們就會呼叫個別的post出來執行,而不會執行在整個Post這個class上頭,這種就是instance method的概念。反之,在呼叫個別的post時我們會使用例如Post.first或是Post.find(1)這樣的method,這些搜尋用的method就是class method,會套用在整個class上面,而非個別的instance上面。

風格上的差異

我們跳脫Rails來看,平常在設定class時,這兩種method就會有觀念上的差異。因為今天我們用的是Ruby,是一種物件導向的語言,所有的東西都是物件,因此『各司其職』這件事情就顯得非常重要。Nick Sutterer在Are Class Methods Evil文章中也提到,物件導向語言最重要的是不能讓觀念搞混,否則編修和維護的難度都會大幅提高。

儘管Nick Sutterer相當反對使用class method,但才書學淺的本人覺得在初學階段,還是必須將兩者分清楚,並且在適當的情況下使用。至於那些高深的辯論和用法,等到已經變成專家了再來煩惱。

Michael Lomniki在其評論文章當中有提到,使用instance method和class method是風格的問題。例如以下兩種寫法,就完全是兩種不同風格:

# 以instance method為主
class Greeting
  def initialize(name)
    @name = name
  end

  def hello
    "Hello, #{@name}"
  end

  def question
    "How are you, #{@name}?"
  end
end

# 實際執行
greeting = Greeting.new("John")
greeting.hello
greeting.question

以上風格比較符合Rails以及一般認為物件導向的寫法,以下直接用class method來做相同的事情,看看差在哪裡:

# 以class mehtod為主
class Greeting
  def self.hello(name)
    "Hello, #{name}"
  end

  def self.question(name)
    "How are you, #{name}?"
  end
end

# 實際執行
Greeting.hello "John"
Greeting.question "John"

在第一個例子中,由於Greeting.new預設會執行initialize這個method,因此一般來說任何變數都是在產生新的instance時帶入,並且藉由instance variable在class當中傳遞。這樣的好處是只要在initialize當中將變數設定好,接下來就可以寫得非常精簡。

反之,在第二個例子中,所有變數都要帶到method當中,看起來顯得有些多餘,這也是class method一個不小心就會讓class變得很冗長又難讀的原因。但對於有些人來說,寫code時可以直接用class method,就可以省去多一個new的困擾。

小結

對新手來說,學習Ruby應該都是從Rails起步,因此還是遵照ActiveRecord那種寫法,將class method和instance method的用法完全隔開,各司其職,避免在維護上自己踩到自己的地雷。等較熟練以後,再來看是否有功能可以直接實作成class method。