include的使用:
在類定義中,引入模塊,使模塊中的方法成爲類的實例方法
extend的使用:
也是在類定義中引入模塊,使模塊中的方法成爲類的類方法
命名空間使用小結:
一般來說,在模塊定一種定義一個類使得這個類能在自己獨立的namespace裏。這樣你的類就不會因爲和其它模塊中的類重名而出問題。
module Foo
class Joy
def initialize(one, two)
puts "one: [#{one}] two: [#{two}]"
end
end
end
module Bar
class Joy
def initialize(something)
puts "Do #{something} already!"
end
end
end
這樣的話,我們就不能這樣定義Joy類了:
Joy.new('a', 'b')
因爲你的當前namespace中沒有Joy這個類,但是,你可以這樣定義:
Foo::Joy.new('a', 'b')
或者
Bar::Joy.new('a crossword puzzle')
因爲Joy在模塊Foo和Bar中都定義了。作爲一個模塊的編寫者,或者一些類似的類,你可能會把它們放到一個模塊定義中。但是作爲模塊的使用者,每次都敲這麼多字母可能比較煩人,所以我們可以使用include方法,如上文所示,在類定義中,引入模塊,使模塊中的方法成爲類的實例方法:
include Bar 或者include Foo
祖先鏈的概念:
在上面的命名空間中,假如我們include的順序爲:
include Foo
include Bar
那麼joy會指向Bar,這是爲什麼呢?那就要說到祖先鏈的概念。先來看一段僞碼:
module Printable
def print
#...
end
def prepare_cover
#...
end
end
module Document
def print_to_screen
prepare_cover
format_for_screen
print
end
def format_for_screen
#...
end
def print
#...
end
end
class book
include Document
include Book
end
首先明確一下祖先鏈的概念,實際上,祖先鏈也是包括模塊的。當一個模塊(類)包含一個模塊(類)時,ruby會把這個模塊加入到該類的祖先鏈中。在這裏,先拋出一個問題,那就是ruby中類和模塊到底有什麼區別,後面應該好好總結一下,在這裏先暫時認爲他們的功能效果是一樣的。
這裏的祖先鏈已經比較明確了,Book沒有明確的超類,它是繼承自Object類, 而Object類又包含了kernel模塊並繼承自BasicObject。Book類包含Docunment模塊,Ruby爲Document模塊創建一個包含類,並把它加入到Book類的祖先鏈上,位置正好位於Book類之上。緊接着,Book類又包含Printable模塊,按照邏輯順序,所以Document在祖先鏈上邏輯順序就又上漲了一位,就出現如我們圖中所示的祖先鏈的順序了。
下面,我們來調用一下這條祖先鏈上的邏輯,所以究竟會調用哪一個模塊上的print方法呢?:
b = Book.new
b.print_to_screen
當我們調用b.print_to_screen方法時,對象b成爲self,並且開始進行方法的查找。Ruby在Document模塊中找到了print_to_screen這個方法,並且這個方法還調用了其他方法,並且這個方法還調用了其他方法,這就包括了print方法。在ruby中。沒有明確指定接收者的調用都會作用於self。因此又開始從Book類(self所屬的類)開始方法的查找,知道找到名爲print的方法。在祖先鏈最近的一處定義就是Printable#print,這個print方法就被調用了。
動態方法
這裏動態方法是指教我們動態地調用和定義方法,消除繁複的代碼。其實調用一個方法實際上就是給一個對象發送一個消息。下面是send方法的使用用例:
methods/dynamic_call.rb
class Myclass
def my_method(my_args)
my_arg * 2
end
end
obj = MyClass.new
obj.my_method(3)
當然,我們也可以使用Object#send方法代替點標識符來調用MyClass#my_method方法:
obj.send(:my_method, 3)
在send方法裏面,我們想要調用的方法變成來參數,這樣就可以在代碼運行的最後一刻決定調用哪個方法,這個技巧被稱爲動態派發。
method_missiing方法
在ruby中,編譯器並不檢查方法調用的行爲,這意味着你可以調用一個並不存在的方法,例如:
methods/method_missing.rb:
class Laywer; end
nick = Lawyer.new
nick.talk.sample
<NOMethodError>
我們先來看看這個方法是這麼查找的。回想一下我們的祖先調用鏈,處在最頂端的是BasicObject類。首先Ruby回到nick對象的類中查詢它的實例方法,如果那裏沒有的話,Ruby就會沿着祖先鏈去查找,最終會來到BasicObject類。
找不到talk_sample方法,ruby就會在nick對象上調用一個名爲method_missing的方法。method_missing這個方法是BasicObject的一個私有實例方法,而所有的對象都繼承自BasicObject類,所以它的所有對象都可用。一般來說,我們是不能直接調用私有方法的,但是可以通過send方法來調用。
nick.send :method_missing, :my_method
<NoMethodError>
我們剛剛做的工作就是就是ruby解釋器所做的工作。我們告訴對象,“我試着調用你的一個名爲my_method方法,但是你不明白我想幹什麼。“BasicObject#method_missing方法會拋出一個NoMethodError進行響應,這是它全部的工作。它就像是一個無主信件的集中處,所有無法投遞的消息最後都會來到這裏。
現在我們來試着覆寫(overwrite)一下method_missing的方法:
class Lawyer
def method_missing(method, *args)
puts "You called: #{method} (#{args.join(',')})"
puts "(You also passed it a blocked)" if block_given?
end
end
bob = Lawyer.new
bob.talk_sample('a','b') do
puts 'block test'
end
覆寫method_missing方法可以讓你調用實際上並不存在的方法。
靜態語言方法和動態語言方法
正如我們所知,代碼中的對象總是在不停地交談,有些語言(如java,和c++)的編譯器會控制這些交談。對每一次方法調用,編譯器都會檢查接收對象是否有一個匹配的方法。這稱之爲靜態類型檢查(static type checking),這稱爲動態語言(static language)。在動態語言(比如Python 和 Ruby)中,則沒有這樣一個像警察一樣的編譯器。因此,我們可以在Laywer對象上調用talk_simple方法,系統不會發出任何警告,直到這個調用真正被執行纔會報錯。這是Laywer類纔會抱怨說自己根本沒有這個方法。