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