【问题标题】:define_method in a class method类方法中的定义方法
【发布时间】:2015-08-25 01:48:04
【问题描述】:

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

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

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

【问题讨论】:

    标签: ruby oop metaclass


    【解决方案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,因此该方法在Cheese 上创建实例方法initialize

    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】:

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

      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之外的self

      类方法Test中的self

      类方法中定义的方法

      在类方法之外定义的方法

      实例方法中的self #

      test.rb:26:in &lt;main&gt;': undefined methoddefine_using_send_inside_class_method' for Test:Class (NoMethodError)

      这段代码:

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

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

      如果我们需要定义一个类方法,send 需要像这样在 eigen 类上调用:

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

      【讨论】:

        【解决方案3】:

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

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

        【讨论】:

        • 谢谢吉姆,我已经用下面显示的简化示例做了一些其他实验,现在我想我对这个问题很清楚了。
        猜你喜欢
        • 2013-10-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-10-24
        • 2010-11-19
        • 1970-01-01
        • 1970-01-01
        • 2020-12-24
        相关资源
        最近更新 更多