之前我們說過,Ruby 沒有函數,只有方法。但 Ruby 有不只一種的方法, 本節將介紹存取控制 (access controls)。
如果我們在「最高層級」(top level) 定義方法,而不是在類別中定義,那會怎樣呢?我們會發現這個方法與 C 這種傳統語言中的函數 (function) 相似。
| n * n
| end
nil
ruby> square(5)
25
我們的新方法不屬於任何類別,但其實 Ruby 將它指定為 Object
類別,即所有類別的父類別。因此,任何物件現在都能使用這個方法。但有個小問題:這是所有類別的私有 (private) 方法。我們之後會解釋這是甚麼意思,但其中一個影響就是它只能以函數的方式呼叫出來,如下所示:
| def fourth_power_of(x)
| square(x) * square(x)
| end
| end
nil
ruby> Foo.new.fourth_power_of 10
10000
我們不能明確地將該方法應用於物件:
ERR: (eval):1: private method `square' called for "fish":String
這樣就能聰明的保留 Ruby 的純物件導向特質(函數仍為物件方法,只是接收器 (receiver) 是隱含的 self
而已)。
物件導向程式設計的常見心智訓練 (mental discipline)(曾在之前章節透露過),在於辨別規格 (specification) 與實作 (implementation),或是物件應該要完成甚麼 (what) 工作,以及如何 (how) 完成。使用者一般不知道物件的內部運作方式,只需要了解輸入輸出的是甚麼,並相信物件的內部運作即可。因此,不同類別擁有外界不知道的方法,有時也是有用的,但這些方法只用於內部(有需要的話,程式員可予以改進,而不改變使用者對於物件類別的看法)。以下範例中,請想像 engine
是該類別的隱形內部運作方式。
| def times_two(a)
| puts "#{a} times two is #{engine(a)}"
| end
| def engine(b)
| b*2
| end
| private:engine # 讓使用者看不到 engine
| end
Test
ruby> test = Test.new
#<Test:0x4017181c>
ruby> test.engine(6)
ERR: (eval):1: private method `engine' called for #<Test:0x4017181c>
ruby> test.times_two(6)
6 times two is 12.
nil
我們會預期 test.engine(6)
傳回 12,但我們在外使用 Test
物件時,engine
方法是不能呼叫的。只有其他 Test
的方法(如 times_two
)才能使用 engine
。我們需要使用公開介面 (public interface) 的 times_two
方法。負責該類別的程式員能夠隨意變更 engine
(可能把 b*2
改為 b+b
,假設引數提高了效率),而不會影響使用者與 Test
物件的互動。這個例子確實比較簡單,但我們創造更複雜有趣的類別時,存取控制的優點就會更明顯。 (編註:這就叫做物件導向的封裝概念)