使用`instance_eval`

时间:2015-10-15 10:04:19

标签: ruby

我正在阅读为什么对ruby的尖锐指南,在第6章中,他使用了这段代码:

class Creature
  # Get a metaclass for this class
  def self.metaclass; class << self; self; end; end

  ...

  def self.traits( *arr )
    # 2. Add a new class method to for each trait.
    arr.each do |a|
      metaclass.instance_eval do
        define_method( a ) do |val|
          @traits ||= {}
          @traits[a] = val
        end
      end
    end
  end
end

为什么他在班级instance_eval的元类上调用Creature?由于instance_eval将方法添加到metaclass,因此他可以执行此操作:

def self.metaclass; self; end;

或者我错了?还有更优雅的解决方案吗?

2 个答案:

答案 0 :(得分:1)

如果你这样做

def self.metaclass; self; end;

您将引用Creature课程。因此,在这种情况下,方法将不是定义对象的单个类Creature,而是定义到类Creature本身(到类Creature的实例方法列表)。方法

def self.metaclass; class << self; self; end; end

是一种在ruby中检索单个类对象Creature的简单方法。 1.9。在ruby 1.9+中实现了方法singleton_class,它是class << self的捷径。所以代码可以简化为:

class Creature

  ...

  def self.traits( *arr )
    # 2. Add a new class method to for each trait.
    arr.each do |a|
      singleton_class.instance_eval do
        define_method( a ) do |val|
          @traits ||= {}
          @traits[a] = val
        end
      end
    end
  end
end

答案 1 :(得分:1)

编写_why代码的简单方法就是

def self.traits( *arr )
  # 2. Add a new class method to for each trait.
  arr.each do |a|
    metaclass.define_method(a) do |val|
      @traits ||= {}
      @traits[a] = val
    end
  end
end

这里唯一的问题是你得到一个错误:

private method `define_method' called for metaclass (NoMethodError)

私有方法只能使用隐式接收器调用,即问题是方法调用之前的显式metaclass.。但是如果我们删除它,隐式接收器(self)就是Creature!那么我们如何将self更改为其他对象? instance_eval

metaclass.instance_eval do
  define_method(a) do |val|
    ...
  end
end

所以它实际上只是绕过define_method私有的事实。另一种破解方法是使用send

metaclass.send(:define_method, a) do |val|
  ...
end

但是现在所有这一切都是完全没必要的;你可以在不修改私有方法的情况下在元类(AKA单例类)中定义方法:

def self.traits( *arr )
  # 2. Add a new class method to for each trait.
  arr.each do |a|
    define_singleton_method(a) do |val|
      @traits ||= {}
      @traits[a] = val
    end
  end
end