物件與方法

Ruby Explained: Objects and Methods

“Everything in Ruby is an Object” is something you’ll hear rather frequently. “Pretty much everything else is a method” could also be said.

Ruby 所有東西都是物件,也可以這麼說,幾乎一切都是方法。

今天的目標是能初步了解 Ruby 的核心概念。在 Ruby 中所有東西都是物件,每一個物件都有一個類別,每個類別則付予給物件許多方法,可以用來做點什麼事,或者問些問題。物件導向 (object-oriented) 給予 Ruby 超乎想像的權力,讓程式人員的人生輕鬆許多。

思考一下:在 Ruby 中的任何”東西”,不只是眼睛所看到的那樣,例如數字 “12”,對 Ruby 來說 “12” 除了是個數字,他還是個物件,能讓你做許多有趣的操作,例如進行加減乘除或者問他問題:

> 12.class
=> Fixnum

> 12.integer?
=> true

> 12 + 3
=> 15

Ruby 賦予所有物件 (Objects) 一大串的方法 (Methods),方法的呼叫是:物件.方法名 (object.methodname),例如:使用 methods 這個方法,就能取得所有和該物件有關的方法。

> 12345.methods
=> #就會回傳 "12345" 這個物件能使用的所有方法

“12345” 的所有方法中也包含基本的運算符號 +、-、*、/,所以原本也得寫成 12345.+123 或者 12345./5,感覺有點囉嗦,還好 Ruby 做了簡化,使用基本運算符號可以省略 “.”,直接輸入 12345+123 也是可以運行的。

有些屬於檢查是否/真假 (true/false) 的方法,通常會在方法名最後加上一個問號 (?),例如:is_a?,這個方法是用於檢查該物件是否是某個類型。::class 則會告訴你該物件屬於何種類別 (class):

> 1.is_a?Integer
=> true   # 1 是整數嗎?
> "hihi".is_a?Integer
=> false
> "hihi".is_a?String
=>true   # "hihi" 是字串嗎?

> 12.class
=> Fixnum
> 1.2.class
=> Float
> "hihi".class
=> String

is_a?::class 這一類會直接回覆給你關於物件本身訊息的方法,稱為反映式方法 (Reflection Methods)。

  • 方法 (Method) 是什麼?

方法就是一些函數指令,也可以當他是個黑箱,你把左邊的東西放進去,搖一搖,然後右邊就會跑出些東西來。每一個方法都會回傳點什麼,就算是沒東西,也會跟你說聲這是 nil

有些方法的”副作用”比他回傳的結果要有用的許多,例如:puts,當你在 IRB 中輸入 puts "hi",這個 puts 方法會先產生副作用:印出輸入的字串 ("hi"),最後再回傳 => nil

> puts"hi"
hi
=> nil

當你在寫自己的方法時,假如你忘記考慮回傳的狀態,很有可能會得到奇怪的結果,所以一定要很清楚自己現在寫了什麼程式碼,然後會輸出什麼回傳結果。

方法 (Methods) 是允許接受傳入值 (inputs),在方法右邊的小括號可以被省略,如:puts("hi")可以寫成 ` puts”hi”`,只要你知道自己在寫什麼,把這些省起來是 ok 的。

1+2 == 3 意思是 1+2 是否會等於 3,也就是會不會回傳 “true”,最明確的寫法是 1.+(2).==(3)

上面那個例子同時適用於方法鏈接 (Method Chaining),就是你串連一堆方法的時候,會先執行左邊的方法,回傳的結果再和右邊的比對,以剛剛的例子 1+2 == 3 來說,先運算左邊的 1+2 = 3,然後再比對左右兩邊 3 == 3 是否為真 ( true)。這種模式就可以讓你優雅地將數行程式碼串接在一起。

  • Bang Methods(Methods!)

Bang Methods 是指 ! 結尾的方法,使用之後會直接更改原物件的值,如:sort!。驚嘆號是要警告提醒你注意:你正在危險的範圍中, 必須小心使用。記住當你在 IRB 中使用一個普通的方法,會回傳一個結果,並保留原本的物件,但是 Bang Methods 會覆寫原本的物件,具有不可逆的破壞性。

> my_numbers = [1, 5, 3, 2]
=> [1, 5, 3, 2]
> my_numbers.sort
=> [1, 2, 3, 5]
> my_numbers
=> [1, 5, 3, 2]
# 仍維持原物件的值

> my_numbers.sort!
=> [1, 2, 3, 5]
> my_numbers
=> [1, 2, 3, 5]
# my_numbers 已被 sort! 方法所覆寫

方法若以 “?” 做結尾,則會回傳 true 或 false。那麼,究竟這些個方法是打哪兒來的呢?

答案就是:類別 (Class),類別像是一把保護傘,賦予物件在該類別下所有的通用方法,舉例來說:你(對就是你本人)是 Person 這個類別的實體,你從 Person 繼承了許多的行為方法,例如:#笑、#走、#說話…等等。

這個在程式中非常有用,因為常常會需要創造某個類別的實體,如果每次都要重複寫同樣的那些方法,感覺就有點蠢。所以將方法寫在類別之中,那麼這個類別下的所有實體就會繼承這些方法。此外類別 (class) 本身也可以進行繼承。例如:Person 這個類別有許多方法,但其中有些方法是繼承至 Mamal 類別或者 LivingThing 類別,你可以使用任何來自於上一代的方法。

在 Ruby 中試試看一個有趣的練習:使用 ::superclass 查問類別的上一代是什麼?如果一直往上追問,會發現所有東西都繼承於 BasicObjectBasicObject 是大多數方法裡的初始物件。

> 1.class.superclass.superclass.superclass.superclass
=> BasicObject
> BasicObject.methods
=> # giant list of methods 一大票方法

::methods 方法使用在類別上,會回傳所有的類別方法,而 ::instance_methods 則會回傳該類別實體的所有方法,例如:

"hello".methods = String.instance_methods
# "hello" 是字串 (String) 這個類別的實體所以和 String.instance_methods 相等,不等於下面:
# String.methods

object_id 可查看物件的 id,…有時候可能會出現奇怪的錯誤,例如:你修改了某個物件,但卻沒有變動,這時候你可以試著查看的 id,可能就會發現你修改的只是該物件的山寨版。

試著寫下你自己的方法,你可以任意的給輸入值命名,語法像這樣: def methodname(argument1, argument2),(參數) 的括號是可以省略的,每個方法絕對會回傳一個 return 的狀態,或者最後一段程式碼的運行結果。

在 IRB 中你也可以直接寫方法,他會自動偵測出你是否尚未完成語法指令,例如:只有寫了 def 開頭,還沒有給一個 end 的結尾,或者是括號沒有成對。

> def speak(words)
>   puts words
>   return true
> end
=> nil   # ignore this
> speak("hello!")
hello!
=> true

輸入值能不能什麼都沒有是個空值呢?要是方法中需要個輸入值 (inputs),但卻什麼都沒有怎麼辦呢?很簡單,只要先指派好預設值,一切就沒問題。

> def speak(words="shhhh")
>   puts words
> end   # implicitly returns what puts returns... nil!
=> nil   # ignore this
> speak   # no input
shhhh
=> nil

Ruby Explained 是用 “In-Plain-Engilish” 來介紹 Ruby 的核心概念,這些概念也常見於其他的介紹中,不過在 Erik Trautman 這裡是免費而且儘量的淺顯易懂。

Erik Trautman 說自己在學習新東西的時候,他希望有人能把他當做五歲小孩,為他進行解釋,Erik 認為這樣是最好的學習方法,而且能避免錯失任何東西。

翻譯:Translate from Erik Trautman