如何在使用Class.new时从类级方法设置类变量?

时间:2018-02-03 00:43:40

标签: ruby metaprogramming

我记得在某处读过以下两段代码在Ruby中相同。

# Number 1
class A
  # body
end

# Number 2
A = Class.new
  # body
end

但是我注意到当我尝试从类级方法设置类变量时,这两种语法会产生不同的结果。

使用第一种语法,变量在类本身上按预期设置。

irb(main):001:0> class A
irb(main):002:1>   def self.foo
irb(main):003:2>     @@a = :banana
irb(main):004:2>   end
irb(main):005:1> end
=> :foo
irb(main):006:0> A.class_variables
=> []
irb(main):007:0> A.foo
=> :banana
irb(main):008:0> A.class_variables
=> [:@@a]
irb(main):009:0> A.class_variable_get :@@a
=> :banana

irb(main):010:0> Class.class_variable_get :@@a
NameError: uninitialized class variable @@a in Class
    from (irb):10:in `class_variable_get'
    from (irb):10
    from /usr/local/bin/irb:11:in `<main>'
irb(main):011:0>

对于第二个,变量在Class本身上设置!

irb(main):015:0> K = Class.new do
irb(main):016:1*   def self.foo
irb(main):017:2>     @@k = :banana
irb(main):018:2>   end
irb(main):019:1> end
=> K
irb(main):020:0> K.class_variables
=> []
irb(main):021:0> K.foo
(irb):17: warning: class variable access from toplevel
=> :banana
irb(main):022:0> K.class_variables
=> [:@@k]
irb(main):023:0> K.class_variable_get :@@k
=> :banana
irb(main):024:0> Class.class_variable_get :@@k
=> :banana

为什么会这样?

在我正在进行的工作中,我必须动态生成一个类,所以我别无选择,只能使用第二种语法。因此,如何确保变量@@k设置在正在定义的新Class对象上,而不是Class本身?

1 个答案:

答案 0 :(得分:2)

两部分不完全相同,声明的词汇范围存在差异。

在第一个定义中,更常见的类声明,定义在词法上限定为类A,因此类变量在A上设置。只有A和从A继承的类才具有类变量。由于Class 继承自A,因此它无法获取类变量。

在第二个定义中,动态类赋值,定义在词法范围内限定为顶级对象,即对象。因此,类变量将设置在Object上,即顶级对象的类。

现在,这具有重大意义。每个对象都继承自Object,每个类都是类Class的一个实例,您在对象空间中的每个类上定义一个类变量。因此,A获取类变量,Class也获取它。尝试定义一个新类F,它也会有它。这就是Ruby尖叫警告的原因:

  

来自toplevel的类变量访问

这是通常避免使用类变量的原因之一。

有多种方法可以解决这个问题。我最喜欢的是,在类实例上使用attr_accessor

K = Class.new do
  class << self
    attr_accessor :fruit
  end

  self.fruit = :banana
end

K.fruit
# => :banana

# The value isn't shared between inherited classes,
# but the variable is:

class L < K
end

L.fruit
# => nil

L.fruit = :mango
# => :mango

K.fruit
# => :banana

编辑:

请记住,这些变量仍然不是线程安全的,并且由所有线程共享。您将需要线程局部变量来确保线程安全。