Ruby 的元编程
如果您使用了一段时间 Ruby,那么到目前为止,您可能已经听到很多次“元编程”这个词了。在元编程的章节中,我会由浅入深带大家了解 Ruby 的元编程。
[TOC]
1. 什么是元编程
元编程是计算机程序的编写,这些计算机程序将其他程序(或它们本身)作为数据写入或操作,或者在编译时完成部分工作,而这些工作原本可以在运行时完成。
在许多情况下,这使程序员可以在与手动编写所有代码相同的时间内完成更多工作,或者为程序提供更大的灵活性,以有效地处理新情况而无需重新编译。
或者,更简单地说:元编程是编写在运行时编写代码的代码,以使您的编程更轻松。
这听上去是不是很疯狂?
简而言之,您可以使用元编程来重新打开和修改类,捕获不存在的方法并即时创建它们,通过避免重复创建DRY(Don’t repeat yourself)代码等等。
Ruby常见的开源框架比如Rails
、Sinatra
都使用了元编程这门技术。
2. 元编程的例子
编程中的一项重要哲学是 DRY(不要重复自己)。多次编写相同(或相似)的代码不仅浪费时间,而且在将来需要进行更改时可能会成为一个极大的困扰。在许多情况下,可以通过编写为您编写代码的代码来消除这种重复工作。
这是一个示例:考虑一个汽车制造商的应用程序,该应用程序可以存储和访问每个模型的数据。在应用程序中,我们有一个名为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
每个汽车模型都具有各种功能,例如“立体声”,“警报”等。我们提供了一种获取和设置汽车每个特征值的方法。每个功能都有信息和价格,因此对于我们添加到CarModel
类中的每个新功能,我们需要定义两个新方法:feature_info
和feature_price
。由于每种方法都相似,因此我们可以执行以下操作来简化此代码:
# 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
的数组,其中包含我们希望为其添加方法的所有功能。
然后,对于每个功能,我们使用Ruby的Module#define_method
为每个功能定义四个方法。就像示例1中一样,四种方法是获取功能价格和信息的getter和setter方法。唯一的区别是,它们是在定义类时动态编写的,而不是由我们动态编写的。我们使用Object#instance_variable_set()
设置每个功能的实例变量的值,并使用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已经拥有可以做到这一点的方法也就不足为奇了。只需一行代码,即可使用Module#attr_accessor
(attr_accessor
被称为为类宏(class macro))与示例2中的功能相同。
这已经很好了,但我们还可以更完美一点。
对于每个功能,我们仍然需要定义两个属性(feature_info和feature_price)。
理想情况下,我们应该能够调用一个与示例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
的每个参数,并将它们传递给具有_price
和_info
扩展名的attr_accessor
。
尽管这种方法比示例3中的方法稍微复杂一些,但它可以确保每个功能都被视为相同,并且意味着将来添加更多属性将更加简单。
注意事项:您可以使用元编程做一些非常酷的事情,例如用很少的代码行添加大量功能,需要注意的是,您一定要注意代码的可读性,过度的元编程会使您的代码难以理解和调试。
这看起来是不是很高大上,让我们开始元编程的学习吧~
3. 小结
本节我们学习了什么是元编程,并用一个实例来讲解了什么是元编程的概念,下节课我们正式进入元编程的其他知识点学习。