Ruby 的元編程
如果您使用了一段時(shí)間 Ruby,那么到目前為止,您可能已經(jīng)聽到很多次“元編程”這個(gè)詞了。在元編程的章節(jié)中,我會(huì)由淺入深帶大家了解 Ruby 的元編程。
[TOC]
1. 什么是元編程
元編程是計(jì)算機(jī)程序的編寫,這些計(jì)算機(jī)程序?qū)⑵渌绦颍ɑ蛩鼈儽旧恚┳鳛閿?shù)據(jù)寫入或操作,或者在編譯時(shí)完成部分工作,而這些工作原本可以在運(yùn)行時(shí)完成。
在許多情況下,這使程序員可以在與手動(dòng)編寫所有代碼相同的時(shí)間內(nèi)完成更多工作,或者為程序提供更大的靈活性,以有效地處理新情況而無(wú)需重新編譯。
或者,更簡(jiǎn)單地說(shuō):元編程是編寫在運(yùn)行時(shí)編寫代碼的代碼,以使您的編程更輕松。
這聽上去是不是很瘋狂?
簡(jiǎn)而言之,您可以使用元編程來(lái)重新打開和修改類,捕獲不存在的方法并即時(shí)創(chuàng)建它們,通過避免重復(fù)創(chuàng)建DRY(Don’t repeat yourself)代碼等等。
Ruby常見的開源框架比如Rails
、Sinatra
都使用了元編程這門技術(shù)。
2. 元編程的例子
編程中的一項(xiàng)重要哲學(xué)是 DRY(不要重復(fù)自己)。多次編寫相同(或相似)的代碼不僅浪費(fèi)時(shí)間,而且在將來(lái)需要進(jìn)行更改時(shí)可能會(huì)成為一個(gè)極大的困擾。在許多情況下,可以通過編寫為您編寫代碼的代碼來(lái)消除這種重復(fù)工作。
這是一個(gè)示例:考慮一個(gè)汽車制造商的應(yīng)用程序,該應(yīng)用程序可以存儲(chǔ)和訪問每個(gè)模型的數(shù)據(jù)。在應(yīng)用程序中,我們有一個(gè)名為CarModel
的類:
# Example 1
class CarModel
def engine_info=(info)
@engine_info = info
end
def engine_info
@engine_info
end
def engine_price=(price)
@engine_price = price
end
def engine_price
@engine_price
end
def wheel_info=(info)
@wheel_info = info
end
def wheel_info
@wheel_info
end
def wheel_price=(price)
@wheel_price = price
end
def wheel_price
@wheel_price
end
def airbag_info=(info)
@airbag_info = info
end
def airbag_info
@airbag_info
end
def airbag_price=(price)
@airbag_price = price
end
def airbag_price
@airbag_price
end
def alarm_info=(info)
@alarm_info = info
end
def alarm_info
@alarm_info
end
def alarm_price=(price)
@alarm_price = price
end
def alarm_price
@alarm_price
end
def stereo_info=(info)
@stereo_info = info
end
def stereo_info
@stereo_info
end
def stereo_price=(price)
@stereo_price = price
end
def stereo_price
@stereo_price
end
end
每個(gè)汽車模型都具有各種功能,例如“立體聲”,“警報(bào)”等。我們提供了一種獲取和設(shè)置汽車每個(gè)特征值的方法。每個(gè)功能都有信息和價(jià)格,因此對(duì)于我們添加到CarModel
類中的每個(gè)新功能,我們需要定義兩個(gè)新方法:feature_info
和feature_price
。由于每種方法都相似,因此我們可以執(zhí)行以下操作來(lái)簡(jiǎn)化此代碼:
# Example 2
class CarModel
FEATURES = ["engine", "wheel", "airbag", "alarm", "stereo"]
FEATURES.each do |feature|
define_method("#{feature}_info=") do |info|
instance_variable_set("@#{feature}_info", info)
end
define_method("#{feature}_info") do
instance_variable_get("@#{feature}_info")
end
define_method "feature_price=" do |price|
instance_variable_set("@#{feature}_price", price)
end
define_method("#{feature}_price") do
instance_variable_get("@#{feature}_price")
end
end
end
在此示例中,我們首先定義一個(gè)名為FEATURES
的數(shù)組,其中包含我們希望為其添加方法的所有功能。
然后,對(duì)于每個(gè)功能,我們使用Ruby的Module#define_method
為每個(gè)功能定義四個(gè)方法。就像示例1中一樣,四種方法是獲取功能價(jià)格和信息的getter和setter方法。唯一的區(qū)別是,它們是在定義類時(shí)動(dòng)態(tài)編寫的,而不是由我們動(dòng)態(tài)編寫的。我們使用Object#instance_variable_set()
設(shè)置每個(gè)功能的實(shí)例變量的值,并使用Object#instance_variable_get
返回每個(gè)功能的實(shí)例變量的值。
# Example 3
class CarModel
attr_accessor :engine_info, :engine_price, :wheel_info, :wheel_price, :airbag_info, :airbag_price, :alarm_info, :alarm_price, :stereo_info, :stereo_price
end
定義這樣的getter和setter方法的需求在Ruby中很常見,因此Ruby已經(jīng)擁有可以做到這一點(diǎn)的方法也就不足為奇了。只需一行代碼,即可使用Module#attr_accessor
(attr_accessor
被稱為為類宏(class macro))與示例2中的功能相同。
這已經(jīng)很好了,但我們還可以更完美一點(diǎn)。
對(duì)于每個(gè)功能,我們?nèi)匀恍枰x兩個(gè)屬性(feature_info和feature_price)。
理想情況下,我們應(yīng)該能夠調(diào)用一個(gè)與示例7相同的方法,但只需列出每個(gè)功能一次即可
# Example 4
class CarModel
# define a class macro for setting features
def self.features(*args)
args.each do |feature|
attr_accessor "#{feature}_price", "#{feature}_info"
end
end
# set _info and _price methods for each of these features
features :engine, :wheel, :airbag, :alarm, :stereo
end
在此示例中,我們采用CarModel#features
的每個(gè)參數(shù),并將它們傳遞給具有_price
和_info
擴(kuò)展名的attr_accessor
。
盡管這種方法比示例3中的方法稍微復(fù)雜一些,但它可以確保每個(gè)功能都被視為相同,并且意味著將來(lái)添加更多屬性將更加簡(jiǎn)單。
注意事項(xiàng):您可以使用元編程做一些非??岬氖虑?,例如用很少的代碼行添加大量功能,需要注意的是,您一定要注意代碼的可讀性,過度的元編程會(huì)使您的代碼難以理解和調(diào)試。
這看起來(lái)是不是很高大上,讓我們開始元編程的學(xué)習(xí)吧~
3. 小結(jié)
本節(jié)我們學(xué)習(xí)了什么是元編程,并用一個(gè)實(shí)例來(lái)講解了什么是元編程的概念,下節(jié)課我們正式進(jìn)入元編程的其他知識(shí)點(diǎn)學(xué)習(xí)。