高見龍

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都有可能透過網址而被存取到,那也許不會是你想要的結果。

Flashers on Google+

image

來勢洶洶的Google+,有人一用了就決定搬家了,有人覺得還好,沒多好用。Google+會不會讓社群網站的版圖重新洗牌呢? 我沒有預言的特異功能,這就留給時間來證明了。

不過的確是有一些國內外的Flasher/ASer也上去註冊了帳號,至於本人是不是真的會搬過來經營,還是留在原本的Twitter/Facebook,這就看個人喜好了。

以下列出了一些我目前在Google+上有看到的Flasher,如果有興趣可以參考看看。我列出來的名單有的是常見面、常聊天的朋友,有的是AS讀書會的朋友,有的是工作上有合作,有的只有在網路上交流過。沒列出來的不代表就不是高手,而是小的我人面不夠廣,認識的人實在不多,或是還沒有這個緣份能認識到這些高手,所以這個名單歡迎大家來補充。

先聲明一下,列這個名單的心態,只是單純的想讓更多的朋友能認識更多的Flasher,也許能透過社群網站知道這些高手們最近在研究什麼心得,或是有新的徵才資訊,或是有案仔要找這些人合作.. 其實很單純的。不過如果有不希望被列出來的也請跟我說,我會馬上處理。

提醒

提醒大家一下,在網路上發言、加好友請記得遵守網路禮儀,特別是Google+的社交圈的規則跟Facebook/Twitter/Plurk是不太一樣的,加了對方之後,即使對方沒把你加入他的社交圈,他還是可能得看到你發表的訊息,這點請多留意。

以下名單依英文字母先後排序,並且可能隨時會更新,最後更新日期:2011/7/21

國外

國內

最後,如果大家不嫌棄,這是小的我自己的

http://www.eddie.com.tw/+ SHAMELESS PROMOTION!!

我想這個名單並沒辦法包括所有的Flasher,我相信還有許多業內的高手是我還沒福份認識到的,至少日本跟大陸的那邊的神人們我還沒機會遇到。以上,如果不希望被列出來的,請跟我說,我會馬上拿下來;如果有知道漏了誰的,或是只是單純的想交朋友的,也歡迎在底下留言補充囉。

記住你青春無畏的樣子

image

其實只是一篇沒什麼內容的口水文!

最近在整理東西的時候發現一枚我在十多年前刻的印章,這是第一次出來闖江湖用的名號。(認識我十年或以上的朋友也許還記得這個當年自以為是的名號)。

正所謂初生之犢不畏虎,年輕時候在學校幫忙寫校務系統,才剛學會用ASP寫點簡單的資料庫新增、修改、刪除功能,然後就自以為放眼望去沒高手了;連在BBS上回答別人問題,也常丟RTFM出來(現在好像也差不多,在PTT Flash版也常叫人去翻F1手冊),現在想想真不知道當年哪來的自信。

不過話說回來,雖然年輕氣盛,但卻很敢試新的玩具,很敢到處挑戰權威,到處筆戰,什麼沒在怕;反而現在年紀大了,不只學習能力跟記憶體有變差的趨勢,不知道是不是喝了太多年的塑化劑,連膽子也都變小顆了。

最近104人力銀行拍了一個廣告:

廣告中的那句台詞「記住你青春無畏的樣子」,整個打到我的點。

可能因為讀的是醫學院的關係,同學大多是去醫院或學校就職,一路上其實沒太多的同學或朋友是可以一起討論。加上我這人又很愛現,學到一點覺得不錯的東西就想找人講,不然會覺得整個不痛快。只可惜我喜歡的東西又通常都不是主流,就算一起工作的同事或主管也不見得有興趣,所以很多時候,我只能把它當做自己下班後的娛樂。

又也許因為工作、小朋友的關係,桌上的書越堆越高,常常覺得怎麼會弄得這麼累,好幾度認真的在考慮也許應該去找家傳統產業的IT部門窩著養老就好。

雖然有了家庭、小孩後,做事情的確是得小心謹慎些,不過這個廣告提醒了我,以前那個熱血年輕人好像還活著,而似乎還沒想這麼早就讓我過爽爽。

感謝活在這個社群媒體(Facebook、twitter、plurk)盛行的時代,加上近來越來越熱絡的實體聚會,以前只能在網路上或書本上遠觀的高手,慢慢的也有機會跟他們交流心得。學習的路上有伴,而且還是高手的伴,的確會讓這條路走得更久一些。

前兩天在網路上看到轉載的影片,主題是「偉大的領導者如何激勵行為」:

看了眼淚差點掉下來,真的是太感動了,講者講了好多我需要改進的地方。我雖然相信「相信」這件事可以化為力量,只可惜很容易幾分鐘熱度就退燒了。

曾經有人問我應該怎麼學好程式? 我簡單的說:「就多練習吧,除非你是天才,不然這一條路沒有捷徑」,大部份的你我都是從不會開始。

不要花太多時間來評估「應該學哪種程式語言最好」,這世上沒有什麼是”最好的”程式語言。只要肯花時間開始學就會有收獲、有進步。我在第五次的AS讀書會的最後一張投影片寫著:

“只要你開始動手做,你一定會變強“

不要想太多,如同某個運動品牌的口號:「Just Do It!」,去做就是了。

雖然目前離我設定的目標還很遠,不過我是這樣走過來的。我不是天才,我可以,我相信大家應該也都可以。謹以此文提醒自己,也希望自己能永遠記得當年開始寫程式的初衷。

共勉之!

第五回AS讀書會之「嗯! 設計API我也會」

image

第五回的AS讀書會,輪到小的來給大家分享自己在過去工作上關於寫程式的一些入門心得。這次有機會可以跟新德老師一起同台,真的是太榮幸了。

以下是影片:

第一段:

第二段:

第三段:

心得

  1. 下次我也要來live demo!!
  2. 還得在修一下講話時候過多的贅字還有講話太快的毛病
  3. 是真的該減肥了.. orz

感謝來參加的大家,也感謝超高效率的攝影師健雄哥的熱情贊助,一個晚上就把影片處理好了! 投影片內容如果有哪邊觀念上是有錯的,還請前輩先進們不吝指教。

咱們下回見!

Ruby裡的方法呼叫

Ruby裡的方法呼叫雖然還滿直覺的,不過偶爾可能會因為位置、角色的不同而會有不同的結果。

雙胞胎?

直接來一段程式碼:

1
2
3
4
5
6
7
8
9
10
11
12
class ObjA
  def func
      puts "this is method func"
  end

  def func
      puts "this is a later func"
  end
end

obj = ObjA.new
obj.func # 猜猜這裡會是什麼?

在 Ruby 裡,如果在同一個 scope 底下定義了兩個同名的 method 並不會發生錯誤,但先定義的 method 會被後面定義的給蓋掉,所以執行這段程式的話:

> ruby test.rb
this is a later func

如果你有打開 Ruby 的警告模式選項(-w),在執行的時候就會出現警告訊息:

> ruby -w test.rb
test.rb:7: warning: method redefined; discarding old func
test.rb:3: warning: previous definition of func was here
this is a later func

血脈相連

再來我們看一下在類別裡的方法呼叫:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class ObjA
  def func
      puts "this is ObjA"
  end
end

class ObjB < ObjA
  def func
      puts "this is ObjB"
      super
  end
end

obj = ObjB.new
obj.func # 猜猜這裡會是什麼?

你在 ObjB 類別裡的看到的那個 super,它會呼叫跟它血緣最近的父類別的同名method。它會延著繼承一直往上找,如果一直找都找不到同名的method的話,則會跳出錯誤訊息:

NoMethodError: super: no superclass method ‘func’

上面這段程式的執行結果:

> ruby test.rb
this is ObjB
this is ObjA
Who’s your daddy?

Ruby 的 OOP 是單一繼承的,藉由引入了 module 的 Mixin 概念,可以讓你有用到多重繼承的好處,又不擔心多重繼承的問題(例如鑽石繼承問題)

來看程式碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
module ModA
  def mod_method
      puts "this is mod_method in ModA"
  end

  def func
      puts "this is ModA"
  end
end

module ModB
  def mod_method
      puts "this is mod_method in ModB"
  end

  def func
      puts "this is ModB"
  end
end

class ObjA
  def func
      puts "this is ObjA"
  end
end

class ObjB < ObjA
  include ModA
  include ModB

  def func
      puts "this is ObjB"
      super
      mod_method
  end
end

obj = ObjB.new
obj.func # 猜猜這裡會是什麼?

在往下看之前,可以先想一下最後一行的 obj.func 會印出什麼答案?

在 Ruby 裡,如果在類別裡有引進了 module,而 module 裡剛好有跟類別定義了同名的 method 的話,會以類別裡定義的方法為主。我們來看一下執行的結果:

> ruby test.rb
this is ObjB
this is ModB
this is mod_method in ModB

第一行的 ”this is ObjB” 沒問題,確實是呼叫 ObjB 的 func,但第二行為什麼是 ModB 而不是 ”this is ObjA”? 不是會呼叫離它最近的父類別同名 method 嗎?

沒錯,的確是呼叫離它最近的父類別同名 method,但被 module 給 mixin 之後,繼承的族譜有稍微變了。我們直接來看一下這行:

1
p ObjB.ancestors # [ObjB, ModB, ModA, ObjA, Object, Kernel]

你會發現 ObjB 的老爸本來應該是 ObjA,但透過 mixin 之後卻變成 ModB、ModA、ObjA,再來才是 Object(如果在 Ruby 1.9 版本之後,上面還有個 BasicObject)。至於這邊的 Kernel 是什麼? 它也是個 mixin 到 Object 裡的一個 module 而已。

所以這個地方的 super 會呼叫離它最近的 func,也就是 ModB 的 func。

那假設我們引入了兩個剛好有定義同名 method,這樣一來又要呼叫誰的? 規則其實也滿簡單的,跟 method 定義有點像,就是比較晚 include 裡來的,會蓋掉前面 include 的模組方法。所以在這邊的第三行會是 ”this is mod_method in ModB”

快快樂樂來寫 code

接下來如果時間允許的話,預計會來把「快快樂樂學Ruby」系列給補完,希望可以在大家在學習 Ruby 的時候,帶來一些些的幫助。

若內容有錯誤或觀念不正確,再請不吝告知,感謝!