Ruby 的元编程

如果您使用了一段时间 Ruby,那么到目前为止,您可能已经听到很多次“元编程”这个词了。在元编程的章节中,我会由浅入深带大家了解 Ruby 的元编程。

[TOC]

1. 什么是元编程

元编程是计算机程序的编写,这些计算机程序将其他程序(或它们本身)作为数据写入或操作,或者在编译时完成部分工作,而这些工作原本可以在运行时完成。

在许多情况下,这使程序员可以在与手动编写所有代码相同的时间内完成更多工作,或者为程序提供更大的灵活性,以有效地处理新情况而无需重新编译。

或者,更简单地说:元编程是编写在运行时编写代码的代码,以使您的编程更轻松。

这听上去是不是很疯狂?

简而言之,您可以使用元编程来重新打开和修改类,捕获不存在的方法并即时创建它们,通过避免重复创建DRY(Don’t repeat yourself)代码等等。

Ruby常见的开源框架比如RailsSinatra都使用了元编程这门技术。

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_infofeature_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_accessorattr_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. 小结

本节我们学习了什么是元编程,并用一个实例来讲解了什么是元编程的概念,下节课我们正式进入元编程的其他知识点学习。