元編程 進一步理解 metaclass
來源:中科服 發(fā)布時間:2015-06-12 文章分類:行業(yè)動態(tài)
分享:
如果你剛開始使用元編程,并且想要使用它的話,下面的四個方法或許會給你一些幫助。
class Object
# 擴展Object類,添加metaclass方法,返回meta-class
def metaclass; class << self; self; end; end
def meta_eval &blk; metaclass.instance_eval &blk; end
# 添加方法到meta-class
def meta_def name, &blk
meta_eval { define_method name, &blk }
end
# 類里創(chuàng)建實例方法
def class_def name, &blk
class_eval { define_method name, &blk }
end
end
我將把這些文件保存到 metaid.rb的文件,為了使用metaclass更方便,我準備創(chuàng)建一個庫。讓我們開始討論metaclass吧,我建議你最好在電腦里也保存一份metaid.rb。
花點時間把這篇文章里的代碼都執(zhí)行一遍,你會發(fā)現(xiàn)對元編程的理解更加深刻了。
關于Class
好吧,什么是Class? 讓我們創(chuàng)建一個簡單的對象了解一下。
class MailTruck #Truck意思是卡車
attr_accessor :driver, :route
def initialize( driver, route )
@driver, @route = driver, route
end
end
m = MailTruck.new( "Harold", ['12 Corrigan Way', '23 Antler Ave'] )
#=>
m.class
#=> MailTruck
對象是保存變量的存儲器。MailTruck是一個對象,當實例化后,會有@driver和@route變量。當然它也可以持有其他任何類型的變量。
m.instance_variable_set( "@speed", 45 )
m.driver
#=> Harold
m.instance_variables
#=> [:@driver, :@route, :@speed]
好吧,@driver實例變量有一個訪問控制器。當Ruby在MailTruck類的定義中看到attr_accessor :driver的時候,你可以獲取Reader和Writer方法。方法 driver以及 driver= 。
這些方法保存在類中。因此實例變量保存在對象里而訪問控制方法(Reader和Writer)保存在類定義里。他們是兩個完全不同的地方。
這是一個很重要的知識: 對象不會保存方法,只有類才能保存
類是對象
好吧,但是類是對象,不是嗎?我的意思是Ruby中的任何東西都是對象,因此類和對象都是對象才對。是什么東西導致他們一樣?
當然,類是對象。你可以在類中運行所有在該類的對象里可以運行的方法。運行下例,他們都會有ID及符號表。
m.object_id
MailTruck.object_id
但是我之前告訴過你: 類保存方法。他們是不同的?,F(xiàn)在我知道你已經(jīng)有些糊涂了,“如果類是對象,對象的創(chuàng)建是基于類,這樣的話不是導致無線循環(huán)了嗎?”
不,沒有。這點我不是很愿意跟你說,但是類不是真正的對象。從Ruby的源代碼,我們看到:
struct RObject {
struct RBasic basic;
struct st_table *iv_tbl;
};
struct RClass {
struct RBasic basic;
struct st_table *iv_tbl;
struct st_table *m_tbl;
VALUE super;
};
注意!一個類它含有m_tbl(一個符號表用來保存方法)和一個父類(指向父類)。
但是讓我考考你。作為Ruby程序員,一個類是一個對象。因為它滿足兩個重要的條件: 你可以在類中保存實力變量及它可以從它是從Object類而來。就這么簡單。
o = Object.new
#=>
o.class
#=> Object
Class.superclass.superclass
#=> Object
Object.class
#=> Class
Object.superclass
#=> BasicObject
Object類處于很頂層的類當只有在其他地方找不到方法時才會加入進來。
Metaclass到底是什么
我們可以假設metaclass為“一個可以定義class的class”。盡管這個定義在Ruby中不怎么行得通。“一個可以定義class的class”,其實就是一個類。
class Class
def attr_abort( *args )
abort "Please no more attributes today."
end
end
class MyNewClass
attr_abort :id, :diagram, :telegram
end
會打印 “Please no more attributes today.”。attr_abort方法可以在定義中被使用。
你常常在Ruby中定義,再定義類。它不是meta,它僅僅是代碼中的一部分。類持有方法。你還覺得它復雜嗎?
既然之前的定義不再有效了, 我認為Ruby的metaclass可以被理解為“一個類,通過這個類對象可以重新定義自己”。
對象是否需要metaclass
對象是無法持有方法的。大部分對象沒必要保存方法。
但是有些時候你希望一個對象能夠保存一些方法。你沒法做到這些。但Matz提供給我們metaclass,它足夠有些可以實現(xiàn)這些功能。
在 YAML庫中,當一個對象輸出時你可以自定義屬性。
require 'yaml'
class << m
def to_yaml_properties
['@driver', '@route']
end
end
YAML::dump m
#=> --- !ruby/object:MailTruck
#=> driver: Harold
#=> route:
#=> - 12 Corrigan Way
#=> - 23 Antler Ave
當你想要在不影響其他對象的前提下dump某個對象為指定的YAML風格時這是很方便的。上例中, 只有m變量中的對象才會以屬性的順序進行輸出。
MailTruck類的其他對象還是按照YAML庫所選擇的順序進行輸出。有
時候我們想要以指定的格式格式顯示字符串而不更改String類(如果更改String類, 會影響你的所有代碼)。
因此m變量中的對象擁有它自己的to_yaml_properties方法。它保存在metaclass。metaclass為對象保存方法并且在繼承鏈中緊挨著對象。
我們還可以用以下幾種方式添加to_yaml_properties**方法。
def m.to_yaml_properties
['@driver', '@route']
end
如果你載入這篇文章的頭部提供的metaid.rb代碼的話,試試以下代碼:
m.metaclass
#=> #>
m.metaclass.class
#=> Class
m.metaclass.superclass
#=> #
m.metaclass.instance_methods
#=> [..., "to_yaml_properties", ...]
m.singleton_methods
#=> ["to_yaml_properties"]
當你使用 ** class << m ** 語法的時候,你正在打開metaclass。 Ruby會調用這些虛擬類(virtual class)。注意 m.metaclass的結果。一個類附加到一個對象:
#=> #>
當一個對象在附加的metaclass中找到方法時,這些方法被引用為對象的單數(shù)方法(singleton method,中文名稱自己理解就行,好像沒有標準翻譯)而不是類的metaclass的實例方法。并且因為只有一個metaclass附加到對象,它被稱呼為單數(shù)方法。
當你使用metaclass的方法時,很容易找到metaclass。一般,你可以使用 ** class << self; self; end **來獲取metaclass。但這個更加簡單。
metaclass是否需要metaclass?
m.metaclass.metaclass
#=> #>>
m.metaclass.metaclass.metaclass
#=> #>>>
試試這些我們創(chuàng)建的無聊的方法。那么我們是否該使用metaclass的metaclass?
好吧,我們使用普通的metaclass做同樣的事情。一個普通的metaclass可以持有某個對象的方法。因此metaclass的metaclasss持有該metaclass的方法?
當然,它就是一個對象!
問題是metaclass的metaclass對于我們來說不是很有用。只有你想深入了解的時候才會使用到它,我們不在這里花太多時間。
m.meta_eval do
self.meta_eval do
self.meta_eval do
def ribbit; "*ribbit*"; end
end
end
end
m.metaclass.metaclass.metaclass.singleton_methods
#=> ["class_def", "metaclass", "constants", "meta_def",
"attr_test", "nesting", "ribbit"]
metaclass只有當做單層使用的時候才有用。你想給某個對象一些方法?;?,你也可以指定的類擁有metaclass。除了這些你也可以保存方法到隱蔽的metaclass,隱蔽到無人可以獲取它?;蛟S某一天你會這么做。誰知道呢。
有一個很重要的點: metaclass。是的,當你給某個對象創(chuàng)建metaclass的時候,在對象的繼承鏈響應之前,截掉方法的調用。但是它不意味著繼承受到進一步metaclass的影響。當你創(chuàng)建metaclass的metaclass時,對對象最開始引用的metaclass沒有任何影響。
我重申之前聲明的類及基于類的創(chuàng)建.
1. 類是對象。這意味著它可以持有變量。
2. metaclass可以持有實例方法。當它被添加到對象時,這些方法會變成單數(shù)方法。這些方法會截掉方法的調用。
你之前是否在類中使用過實例變量?不是類方法,而是類本身。
class MailTruck
@trucks = []
def MailTruck.add( truck )
@trucks << truck
end
end
為什么不直接使用類變量
class MailTruck
@@trucks = []
def MailTruck.add( truck )
@@trucks << truck
end
end
他們工作的完全一樣,我的意思是沒關系,不是嗎?
下面有兩個原因你需要使用類變量而不是實例變量:
1. 類變量有兩個@@符號,易于辨認
2. 類變量在需要的時候可以被實例方法引用。
class MailTruck
@@trucks = []
def MailTruck.add( truck )
@@trucks << truck
end
def say_hi
puts "Hi, I'm one of #{@@trucks.length} trucks!"
end
end
但是下面的無法執(zhí)行
class MailTruck
@trucks = []
def MailTruck.add( truck )
@trucks << truck
end
def say_hi
puts "Hi, I'm one of #{@trucks.length} trucks!" #在這里@trucks為nil
end
end
那么實例變量有什么好呢?多浪費空間!我再也不使用了!(是的,當碰到上面的情形時使用類變量)
讓我再指出上例中有metaclass出現(xiàn),因為每個類方法都保存在metaclass中。
你也可以使用self:
class MailTruck
def self.add( truck )
@@trucks << truck
end
end
或singleton語法
class MailTruck
class << self
def add( truck )
@@trucks << truck
end
end
end
class MailTruck
def self.company( name )
meta_def :company do; name; end
end
end
上面的代碼很簡單,但很實用。一個新的company類方法添加到了MailTruck,該類方法可以被使用在類定義中。
class HappyTruck < MailTruck
company "Happy's -- We Bring the Mail, and That's It!"
end
好吧,上面的代碼執(zhí)行了HappyTruck的company方法,參數(shù)為它的口號。在這里 meta_def都做了什么?
meta的威力在這里顯現(xiàn)出來了。 meta_def添加了一個叫company的方法到metaclass中。這里的奇妙之處為該方法時添加到了派生類HappyTruck,而不是MailTruck
這個看起來很簡答,但很有用。你可以很簡單的寫出類方法,實現(xiàn)添加類方法到派生類。
返回列表