初探 Ruby Metaprogramming
51CTO推薦專題:Ruby On Rails開發教程
Classes are open
我們先看一段代碼:
- class String
- def say_hello
- p "Hello!"
- end
- end
- "Fred".say_hello
這里我們看到我們reopen了String這個build-in的class,而且添加了一個新的方法say_hello(.NET 3.5中通過擴展方法也實現了這個特性,但ruby的實現更加自然和靈活)這樣使得ruby語言自身提供了很大的可擴展性,而這種從編程語言層面提供的可擴展性為好處體現在兩個方面。
第一,對于ruby語言自身,在其以后的版本中可以對原有類在不破壞原有代碼的基礎之上提供更多更好的方法。.NET 3.5 已經通過擴展方法這個新特性,在原有集合類的方法之外增加了一些新的查詢方法。
第二,對于ruby的使用者,也就是我們這些ruby程序員來說。classes are open,這就意味我們可以更加實現我們一些具體的特殊的需求。例如,我們希望我們應用的程序中的String都可以提供一個encrype的方法,來實現加密。又或者我們對于String類的to_s方法的實現覺得不夠滿意,我們都可以reopen String這個類,然后定義我們的方法。因為ruby的方法查找遵循
”Define a method twice inside the same class, the second method definition takes precedence“
所有我們毋需擔心,我們對于to_s的調用出問題。
前面我說道,ruby的open class比.NET提供的擴展方法更加靈活。而這個靈活體現在我們可以針對一個instance去增加方法,如下
- <SPAN style="FONT-FAMILY: 黑體">fred = 'fred'
- def fred.say_hello
- p 'hello'
- end
- fred.say_hello
- </SPAN>
這樣就滿足了我們對于一些特殊instance的需求。
Definition are active
- class Logger
- if ENV['debug']
- def log
- 'debug'
- end
- else
- def log
- 'non-debug'
- end
- end
- end
這是一段非常簡單的代碼,但是我們可以看到我們是否定義debug這個ENV對于我們的程序會有完全不一樣的行為。這里也許有人會說靜態語言的條件編譯同樣能完成這樣的任務。那么我們就再看一段代碼
- <SPAN style="FONT-FAMILY: 黑體">result = class Fred
- puts 'Hello'
- x = 3
- end
- puts result
- </SPAN>
執行這段代碼,我們會看到這樣的輸出結果:
Hello 3
為什么會輸出Hello呢?因為definition are active,也就是定義本身就是一段可執行的代碼。為什么會輸出3呢?因為ruby中所有的可執行代碼都會有返回值。到這里肯定會有人問,那么class定義中的method呢?你可以試試在irb中定義一個method,你會發現在irb會返回一個nil給你。
但是definition are active在我們實際開發中有什么用呢?那讓我們看一下一個rails的應用
- module ActiveRecord
- class Base
- def has_many models
- end
- def belongs_to model
- end
- end
- end
- class Order < ActiveRecord::Base
- has_many :items
- end
- class Item < ActiveRecord::Base
- belongs_to :order
- end
你能想想如果definition aren't activity, 還會有這樣優雅的代碼嗎?
All methods have a receiver
在ruby中,方法的調用是以message的形式發送給相應的instance的。比如說foo.hello(),就是發送hello這個message給foo。這里很多人會好奇,那么如果我在irb上直接定義方法呢?其實ruby里面有一個概念叫top level execution, 它是一個Object的instance叫做main。當你直接在irb中定義一個方法或者執行一個方法(例如puts "hello"),同樣你只是發送了一個message,而這個message的receiver就是top level execution。
ruby代碼的執行是與當前代碼所在context相關,不同的context關聯不同的receiver。也就是當你的代碼在不同的context下執行,由于context關聯的receiver不同也就有了不同的結果。
- class Context
- def name
- "smith"
- end
- p name
- def hi
- p name
- end
- end
- Context.new.hi
結果為:
"Context" "smith"
如果你想知道在你當前context下你方法的receiver,可以通過在當前context下調用self來獲得。
Class are Object
我們都知道一個object有什么樣的行為和屬性是在ruby中由它的class決定。比如
- class Person
- attr_reader :name
- def initialize(name)
- @name = name
- end
- def introduce
- "I'm #{@name}."
- end
- end
- p = Person.new "Dave"
對于這個例子中,p具有什么樣的行為和屬性是由Person這個class決定的。可是我們看到對于Person我們調用了一個new的方法,那么這個new方法是由誰定義的呢?很簡單啊,我們知道p的行為和屬性由它的class也就是Person決定,那么Person的new方法應該也來自它的class。也就是引出了Class對象,Class對象中有兩個new方法,一個是class method另一個是instance method。我們的Person.new自然調用的就是Class對象中叫new的instance method, 那么那個叫做new的class method有什么用呢?
- Person = Class.new do
- attr_reader :name
- def initialize(name)
- @name = name
- end
- def introduce
- "I'm #{@name}."
- end
- end
這段代碼可以實現之前那段代碼一摸一樣的功能,而這里調用的就是Class中叫做new的class method。最奇怪的Class的superclass是Module,而Module的superclass是Object,但是Class的class是自身,Module的class是Class,而Object的class也是Class(superclass是Class的方法,class是Object的方法),我們也可以說ruby中所有的Object的class都是Class(nil的class是NilClass,但是NilClass的class是Class)。Class間接繼承Object,但是Object的class又是Class,一個典型“雞生蛋,蛋生雞”的問題。這個問題給我最大困惑則是:如果我調用一個對象例如上面例子中p的XX方法,而這個XX方法并沒有直接在Person中定義,那么這個XX方法是來自Class還是Object呢?而對于這一點ruby的解決辦法是在方法的查找receiver的時候,會先檢查Person有沒有這個XX方法,會先檢查Class后檢查Object,也就是先檢查一個class的class,然后檢查superclass。
原文鏈接:http://www.cnblogs.com/feihe/archive/2011/04/17/1951274.html
【編輯推薦】