3

运行此代码:

module A
  def self.included(klass)
    klass.send(:cattr_accessor, :my_name)
  end

  def set_my_name_var
    @@my_name = 'A' # does NOT work as expected
  end

  def set_my_name_attr
    self.class.my_name = 'A' # works as expected
  end
end

class B
  include A

  cattr_accessor :my_other_name

  def set_my_other_name_var
    @@my_other_name = 'B' # works
  end

  def set_my_other_name_attr
    self.class.my_other_name = 'B' # works
  end
end

b = B.new

b.set_my_other_name_var
puts "My other name is " + B.my_other_name
b.set_my_name_var
puts "My name is " + B.my_name

b.set_my_other_name_attr
puts "My other name is " + B.my_other_name
b.set_my_name_attr
puts "My name is " + B.my_name

像这样中断:

My other name is B
TypeError: (eval):34:in `+': can't convert nil into String

如果我们交换最后两个代码块(以便b.set_my_name_attr在之前调用b.set_my_name_var),一切正常。

看起来它被视为@@my_name模块的类变量A,而不是类B(正如我所期望的那样)。不是很混乱吗?在哪里可以阅读有关模块类变量的更多信息?

4

1 回答 1

3

当你set_my_name_var在模块中有你的方法时,A这样做@@my_name = 'A'就是在A. 当通过包含类调用该方法时,此行为不会改变。这也导致了另一个有时会引起人们注意的事实 - 如果您要包含在多个类中,那么每个包含类A只有一个实例@@my_name而不是一个实例。以下示例说明了这一点:

module Example
  def name=(name)
    @@name = name
  end

  def name
    @@name
  end
end

class First
  include Example
end

class Second
  include Example
end

irb(main):066:0> f = First.new
=> #<First:0x2d4b80c>
irb(main):067:0> s = Second.new
=> #<Second:0x2d491d8>
irb(main):068:0> f.name = 'Set via f'
=> "Set via f"
irb(main):069:0> s.name
=> "Set via f"

更新

我想我已经弄清楚发生了什么,这将解释为什么它似乎不像你期望的那样工作。cattr_reader(以及扩展名cattr_accessor)包含以下内容:

class_eval(<<-EOS, __FILE__, __LINE__)
  unless defined? @@#{sym}  # unless defined? @@hair_colors
    @@#{sym} = nil          #   @@hair_colors = nil
  end

  # code to define reader method follows...

发生以下顺序:

  • B被定义为
  • A包含模块
  • included回调确实klass.send(:cattr_accessor, :my_name)如此。
  • an在设置为@@my_name的类中创建。Bnil

如果没有cattr_accessorthen after 调用set_my_name_var,当您@@my_name在其中说它时,B它会引用模块的变量。但是cattr_accessor在类中现在存在一个具有相同名称的变量,所以如果我们说@@my_name在里面,B我们会得到B' 变量的值而不是A's。这就是我说的掩饰的意思。(B的变量妨碍了我们看到A's)

也许下面会说明。想象一下,我们刚刚达到了您的水平b = B.new,并且我们执行了以下操作:

>> A.class_variables
=> [] # No methods called on A yet so no module variables initialised
>> B.class_variables
=> ["@@my_other_name", "@@my_name"] # these exist and both set to nil by cattr_accessor
>> B.send(:class_variable_get, '@@my_name')
=> nil # B's @@my_name is set to nil
>> b.set_my_name_var # we call set_my_name_var as you did in the question
=> "A"
>> A.send(:class_variable_get, '@@my_name')
=> "A" # the variable in the module is to to 'A' as you expect
>> B.send(:class_variable_get, '@@my_name')
=> nil # but the variable in the class is set to nil
>> B.my_name
=> nil # B.my_name accessor has returned the variable from the class i.e. nil

如果您尝试在 setter 之前使用 getter,我认为cattr_reader这样做是为了避免错误。uninitialized class variable(类变量nil的默认值与实例变量不同。)

于 2010-07-16T11:30:38.620 回答