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