当attr_accessor在类方法中时会发生什么?

时间:2017-02-01 18:24:42

标签: ruby attr-accessor

所以这出现在我脑海中,并想知道下面的一些事情发生时会发生什么。

 class Test 
   def self.abc
     attr_accessor :John 
   end
 end

 object = Test.new
 puts "before calling class method abc:  #{object.class.instance_methods(false)}"
 Test.abc
 puts "after calling class method abc:   #{object.class.instance_methods(false)}"

我在这里检查的是,以这种方式创建getter和setter方法。如果是这样,那些实例方法或类方法。首先,我创建一个新对象,然后查看该对象的实例方法。在下一行之后,我运行class方法abc,然后再次检查object的实例方法。那时我只能看到两种方法JohnJohn=。这是怎么发生的?为什么运行类方法会动态地向已创建的对象添加方法?有人可以解释一下这个。

代码的输出是:

before calling class method abc:  []
after calling class method abc:   [:John, :John=]

2 个答案:

答案 0 :(得分:7)

在类方法中,self是类本身。因此,以下内容与他们最终做的相同:

class Test
  def self.abc
    # `self` in this context is Test.
    # This sends the message `:attr_accessor, :john` to `Test`
    attr_accessor :john
  end
end

class Test
  # `self` in this context is Test.
  # This sends the message `:attr_accessor, :john` to `Test`
  attr_accessor :john
end

但是,正如您所指出的,在解析类时不会执行Test::abc,因此不会调用attr_accessor,并且不会添加实例方法。在运行时执行此操作是完全有效的,事实上,它是Rails中执行的大部分元编程的基础。

通常,如果您希望通过类方法添加访问器,则可以在定义之后调用该类方法,但仍然在类声明期间调用:

class Test
  def self.abc
    attr_accessor :john
  end

  abc
end

这实际上会运行,并在类上正确声明访问器!

关于你的问题:

  

如何运行类方法动态地向已创建的对象添加方法?

这是因为实例化一个类并没有实例化"快照"在实例化时类的类 - 它创建了一个对象,该对象将其大部分功能(包括在其上发现实例方法)委托给与之关联的类。请注意,可以在实例上定义新方法,这些方法也不会延伸回类:

class Test
  attr_accessor :foo
end

t1 = Test.new
t2 = Test.new

Test.send(:define_method, :bar) {}
puts t1.respond_to? :foo  # => true
puts t2.respond_to? :foo  # => true

puts t1.respond_to? :bar  # => true
puts t2.respond_to? :bar  # => true

t1.define_singleton_method(:baz) {}

puts t1.respond_to? :baz  # => true
puts t2.respond_to? :baz  # => false

答案 1 :(得分:2)

首先,请注意代替

object = Test.new
object.class.instance_methods(false)

你可以简单地写

Test.instance_methods(false)

所以让我们简化一下:

puts "before calling class method abc:  #{Test.instance_methods(false)}"
  # (prints) []
Test.abc
puts "after calling class method abc:   #{Test.instance_methods(false)}"
  # (prints) [:John, :John=]

这个行为有一个简短的解释,但也有一个较长的解释。

简短说明

Test.method(:attr_accessor)
  #=> #<Method: Class(Module)#attr_accessor> 

此返回值以及

的事实
Test.class #=> Class

告诉我们attr_accessorClass的instance_method,因此是Test的方法。我们确认一下。

Class.instance_methods.include?(:attr_accessor)
  #=> false

糟糕! (你期待=> true?)。这只能意味着attr_accessor是受保护的或私有的实例方法:

Class.protected_instance_methods.include?(:attr_accessor)
  #=> false

Class.private_instance_methods.include?(:attr_accessor)
  #=> true

因此

Class.private_instance_methods.include?(:attr_accessor)
  #=> true

所以attr_accessor只是Test的类Class的私有实例方法,使其成为Test的私有方法。表达式(从早期开始)

Test.method(:attr_accessor)
  #=> #<Method: Class(Module)#attr_accessor> 

还告诉我们attr_accessorClass的超类Module中定义。

Class.superclass
  #=> Module
Class.ancestors
  #=> [Class, Module, Object, Kernel, BasicObject] 
Class.instance_method(:attr_accessor).owner
  #=> Module 
Test.method(:attr_accessor).owner
  #=> Module 

更长的解释

方法Module#attr_accessor可以通过两种方式之一提供给班级Test。这是Ruby的对象模型的本质。 (我已经回避了单例类方法的地方。)

1。测试从其超类

继承该方法
Test.superclass
  #=> Object
Test.ancestors
  #=> [Test, Object, Kernel, BasicObject]

让我们看看。

Object.method(:attr_accessor)
  #=> #<Method: Class(Module)#attr_accessor> 
Object.public_methods.include?(:attr_accessor)
  #=> false 
Object.private_methods.include?(:attr_accessor)
  #=> true

2。 attr_accessorTest的类

的实例方法
Test.class
  #=> Class

Class.private_instance_methods.include?(:attr_accessor)
  #=> true

所以Test都从其超类继承attr_accessor并将其作为其类的实例方法提供。这种二元性通常用this one等图表来解释。

Ruby首先检查Test的类Class,以获取实例方法attr_accessor。它会在哪里找到它。 (如果她没有在那里找到它,那么她会在超类中寻找方法(而不是实例方法)。)