类方法中的define_method

时间:2015-06-10 01:39:37

标签: ruby oop metaclass

在下面的代码中,模块被扩展,这意味着方法hash_initialized被视为类方法或特征类的实例方法。这是我们需要的,因为hash_initialized是在特征类的上下文中调用的。

我不明白的是,如果模块的上下文是特征类,那么define_method应该创建一个名为"初始化"的实例方法。特征类,或者换句话说,类奶酪的类方法。我们不需要实例方法"初始化"这里吗?

module HashInitialized
  def hash_initialized(*fields)
    define_method(:initialize) do |h|
        missing = fields - h.keys
      raise Exception, "Not all fields set: #{missing}" if missing.any?

      h.each do |k,v|
        instance_variable_set("@#{k}", v) if fields.include?(k) 
      end
    end
  end
end

class Cheese
  extend HashInitialized
  attr_accessor :color, :odor, :taste
  hash_initialized :color, :odor, :taste
end

3 个答案:

答案 0 :(得分:3)

调用extend在技术上将模块放在它被调用的特征对象的查找链中,在这种情况下,它与类对象相同。所以你认为hash_initialized的上下文是类是正确的。另外,您认为define_method的上下文是类是正确的。但是,您的最后一步是不正确的。在该上下文中调用define_method时,它定义了一个实例方法,而不是单例方法。

IOW,当您在上下文中调用define_method时,它会在def在该上下文中定义它的同一位置定义方法。

答案 1 :(得分:1)

如果您遇到类似这样的难看,请尝试使用puts self语句对您的代码进行腌制:

module HashInitialized
  puts "self when parsed=#{self}"
  def hash_initialized(*fields)
    puts "self within hash_initialized=#{self}"
    define_method(:initialize) do |h|
      missing = fields - h.keys
      raise ArgumentError, "Not all fields set: #{missing}" if missing.any?
      fields.each { |k| instance_variable_set("@#{k}", h[k]) } 
    end
    private :initialize
  end
end
  #-> self when parsed=HashInitialized

class Cheese
  extend HashInitialized
  attr_accessor :color, :odor, :taste
  hash_initialized :color, :odor, :taste
end
  #-> self within hash_initialized=Cheese

如您所见,self是类Cheese,而不是Cheese的singleton_class。因此,Module#define_method的接收方为Cheese,因此该方法必须在initialize上创建实例方法Cheese

Cheese.instance_methods(false)
  #=> [:color, :color=, :odor, :odor=, :taste, :taste=] 

initialize不在Cheese上创建的实例方法中,因为我稍微修改了代码以使其成为私有方法:

Cheese.private_instance_methods(false)
  #=> [:initialize]

我还略微修改了为实例变量赋值的代码,并使异常类型更具体。

如果合适,您可以将参数测试更改为:

raise ArgumentError, "Fields #{fields} and keys #{h.keys} don't match" if
  (fields-h.keys).any? || (h.keys-fields).any?

您可能希望initialize创建评估员:

module HashInitialized
  def hash_initialized(*fields)
    define_method(:initialize) do |h|
      missing = fields - h.keys
      raise ArgumentError, "Not all fields set: #{missing}" if missing.any?
      fields.each do |k|
        instance_variable_set("@#{k}", h[k])
        self.class.singleton_class.send(:attr_accessor, k)
      end
    end
    private :initialize
  end
end

class Cheese
  extend HashInitialized
  hash_initialized :color, :odor, :taste
end

Cheese.new :color=>'blue', odor: 'whew!', taste: "wow!"
  => #<Cheese:0x007f97fa07d2a0 @color="blue", @odor="whew!", @taste="wow!"> 

答案 2 :(得分:0)

通过简化上面的示例并添加了一些打印输出,我已经明确了这一点。

class Test
  def self.define_something
    define_method(:inside_class_method){puts "method defined inside a class method"}
    puts "self inside class method "+self.to_s
    proc = Proc.new{puts "method defined using send inside class method"}
    self.send(:define_method, :define_using_send_inside_class_method, proc)
  end

  class << self
    puts "self inside eigen class "+self.to_s
  end

  def test
    puts "self inside of instance method "+self.to_s
  end
  puts "self outside of class method "+self.to_s
  define_method(:outside_class_method){puts "method defined outside a class method"}
  define_something
end

Test.new().inside_class_method
Test.new().outside_class_method
Test.new().test
Test.define_using_send_inside_class_method

此代码产生以下输出:

自我特征类#

类方法测试之外的自我

自我内部类方法测试

在类方法中定义的方法

在类方法外定义的方法

自我实例方法#

test.rb:26:in <main>': undefined method define_using_send_inside_class_method&#39; for Test:Class(NoMethodError)

此代码:

self.send(:define_method, :define_using_send_inside_class_method, proc)

它还定义了一个实例方法,因为它是在self上调用的,self是指类Test。

如果我们需要定义一个类方法,需要在特征类上调用send,如下所示:

class << self
  self.send(:define_method, :define_using_send_inside_class_method, proc)
end