高見龍

iOS app/Ruby/Rails Developer & Instructor, 喜愛非主流的新玩具 :)

Public, Protected and Private Method in Ruby

如果你曾經在別的程式語言寫過OOP,你也許對類別的方法存取限制不會太陌生。類別的方法的存取限制常見的有三種:public、protected以及private。

這三種存取限制,比較常聽到的解釋大概會是像這樣:

“public就是所有的人都可以直接存取,private是只有在類別內部才可以存取;而protected差不多是在這兩者之間,比private寬鬆一些,但又沒有public那麼自在,protected在同一個類別內或是同一個package,或是繼承它的子類別可以自由取用,但如果不是的話則不可存取。”

Ruby也有類似的方法存取限制,為什麼特別說「類似」,前面又為什麼需要特別提「大家常聽到的解釋」,因為Ruby在這部份的實作是不太一樣的,待會後面會再詳細說明。

怎麼做?

先來看看怎麼寫,Ruby的方法存取限制有兩種寫法,一種是寫在方法定義之前:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Father

  def method_a
      puts "I am METHOD A in #{self.class}"
  end

  def method_b
      puts "I am METHOD B in #{self.class}"
  end

  protected
  def method_c
      puts "I am METHOD C in #{self.class}"
  end

  private
  def method_secret
      puts "I am spider man in #{self.class}"
  end

end

在Ruby的類別裡,方法只要沒有特別限制預設就是publilc的,除了一個例外,就是initialize方法,它永遠是private的,只會被new方法呼叫。

把存取控制放在前面的這種寫法,只要在它設定之後的方法定義都會受影響,除非又遇到另一個存取控制的設定。在上面的這段程式碼,method_a跟method_b沒有特別限制,所以是public方法(如果你想要特別加上public也沒問題,只是通常不會這麼做),method_c是protected方法,而method_secret則是屬於private方法。

另一種的方法存取限制是寫在方法定義之後:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Father

  def method_a
      puts "I am METHOD A in #{self.class}"
  end

  def method_b
      puts "I am METHOD B in #{self.class}"
  end

  def method_c
      puts "I am METHOD C in #{self.class}"
  end

  def method_secret
      puts "I am spider man in #{self.class}"
  end

  protected :method_c
  private :method_secret

end

哪種比較好?

這兩種寫法哪種方法比較好? 都好,隨個人喜好。我個人喜好第一種,因為我習慣會先把public的方法放在類別的上半部,而把private方法放在類別的最底下,所以使用第一種寫法對我來說寫起來比較順手。

其實public、protected以及private這三個在Ruby裡並不是關鍵字,它也只是Ruby裡的方法而已。

哪裡不一樣?

前面為什麼會特別提到Ruby的方法存取限制跟其它的程式語言「類似」呢? 雖然Ruby裡的確也有public、protected以及private,但事實上是不太一樣的,特別是private方法。我們先來看一小段的程式碼:

1
2
3
4
5
father = Father.new
father.method_a        # I am METHOD A in Father
father.method_b        # I am METHOD B in Father
father.method_c        # NoMethodError
father.method_secret   # NoMethodError

father是Father類別生出來的實體,而實體的public方法如所預期的印出結果,protected跟private方法呼叫的時候產生NoMethodError的例外,看起來很正常,那到底是哪邊不太一樣?

我們再來做個叫做Son的子類別,繼承自Father類別:

1
2
3
4
5
6
7
8
9
10
11
class Son < Father

  def son_method_c
      method_c
  end

  def son_method_secret
      method_secret
  end

end

我給Son類別加了兩個方法,分別會呼叫Father類別的protected跟private方法,再來看範例:

1
2
3
4
5
6
7
son = Son.new
son.method_a           # I am METHOD A in Son
son.method_b           # I am METHOD B in Son
son.method_c           # NoMethodError
son.method_secret      # NoMethodError
son.son_method_c       # I am METHOD C in Son
son.son_method_secret  # I am spider man in Son

在子類別呼叫父類別的protected方法,這不是新鮮事,但你注意到了嗎? 在子類別裡可以直呼叫父類別的private方法耶!

先來看這行:

1
son.method_a

一般我們會把這行翻譯成:

“有一個物件叫做son,然後呼叫了son物件的method_a方法”

不過如果你曾經認識過Smalltalk或是Objective-C的話,你會發現他們會把這行翻譯成:

“有一個接收者(receiver)叫做son,然後對著這個recevier送了一個叫做method_a的訊息(message)”

為什麼特別提這個? 因為在Ruby裡的private方法,只要沒有明確的指出recevier的話就都可以呼叫。所以在上面例子裡的Son類別,即使是呼叫父類別的private方法,只要不要有recevier,它就不會有錯誤產生。

也就是因為這樣,在Ruby的private方法其實不只類別自己內部可以存取,它的子類別也可以。再來看一下這段程式碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class A

  def a
      self.b
  end

  private
  def b
      puts "Hello, This is Private B!"
  end

end

the_a = A.new
the_a.a        # NoMethodError

在A類別的a方法呼叫類別內部的b方法,看起來很合理吧,但實際執行就會出錯NoMethodError的例外,說你存取到了private方法了!! 為什麼? 因為你在呼叫方法b的時候加上了self,前面有提到”在呼叫Ruby的private方法時,不能明確的指定recevier”,在這裡卻明確的指出了self,所以出現錯誤訊息了。沒錯,連self也不行。

我們很常用的puts方法,它就是Object這個類別的private方法之一(更正確的說,是Kernel模組mixin到Object類別裡的方法)。我們平常會這樣用:

1
puts "Hello Ruby"

但如果你這樣做:

1
self.puts "Hello Ruby"

就會跳出NoMethodError

Protected方法?

那protected方法呢? 它的規定就沒那麼嚴格了,你要指定或不指定recevier都可以;至於public方法,就跟其它語言的定義差不多,隨便你用啦。

真的這麼private?

不過,其實Ruby的private方法也不是真的那麼private,轉個彎,一樣可以被外部呼叫:

1
2
3
father = Father.new
father.method_secret          # 是我們預期的 NoMethodError 沒錯
father.send(:method_secret)   # I am spider man in Father

前面提到的puts其實也可以改寫成:

1
Object.send(:puts, "Hello Ruby")  # Hello Ruby

這..這樣會不會有點扯? 如果連private都能被直接存取,那當初何必還要這樣設計呢? 還是直接乾脆全部都public就好了?

我想這其實是Ruby當初設計的哲學之一,Ruby把很大部份的權利都下放給程式設計師,讓開發者有最大的彈性空間可以運用(或惡搞),也就是這樣,在Ruby做Metaprogramming是相對的比在別的程式語言容易的。不只在這裡,你應該還可以在很多地方看到這個Ruby的專屬特性。

僅供參考

如果說這些存取限制只是”參考用”,那到底什麼時候會用到?

雖然說它只是”參考用”,我個人還是會把它當做是程式碼的寫作準則。雖然你可以透過send方法來存取private方法,但不代表你就應該這樣做。而且它也不是真的那麼沒有用,像是在寫Rails的時候,Controller裡的Action預設都是public的,如果你的routes.rb如果把路徑的對應全部打開,那所有的Action都有可能透過網址而被存取到,那也許不會是你想要的結果。

Comments