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