【问题标题】:Class variables leaking when defined from a module?从模块定义时类变量泄漏?
【发布时间】:2017-11-28 07:26:34
【问题描述】:

假设我有一个模块XYZ。当包含在一个类中时,它会扩展基类并在其中添加一个类变量@@foo。它还使用方法扩展类以执行一些getset

module XYZ
  def self.included(base)
    base.send :extend, ClassMethods
    base.class_eval do
      @@foo ||= []
    end
  end

  module ClassMethods
    def foo
      class_variable_get("@@foo")
    end

    def foo=(arg)
      class_variable_set("@@foo", arg)
    end

    def dynamic_set(*args)
      foo += args # This doesn't work
    end

    def dynamic_set_2(*args)
      class_variable_set("@@foo", class_variable_get("@@foo") + args)
    end
  end
end

现在,考虑一下用法:

class A
  include XYZ
end
A.foo #=> []
A.class_variable_get("@@foo") #=> []
A.dynamic_set 1, 2 #=> NoMethodError: undefined method `+' for nil:NilClass
A.dynamic_set_2 1, 2 #=> [1,2]
A.foo #=> [1,2]
A.class_variable_get("@@foo") #=> [1,2]

sn-p 有意义并且可以完成工作,但我无法弄清楚为什么 A.dynamic_set 1, 2 不起作用。

来到问题的主要部分 - 如果我将新的 class B 定义为:

class B
  include XYZ
end
B.foo #=> [1,2] => Why? How did B.foo get these values?
B.class_variable_get("@@foo") #=> [1,2] => Why?
B.dynamic_set_2 3, 4
B.foo #=> [1,2,3,4]
A.foo #=> [1,2,3,4]

@@foo 在类级别定义时(使用class_eval),为什么BA 共享同一个类变量?

我了解使用类变量和类实例变量的含义。只是想弄清楚为什么这不能按预期工作,以清除一些概念:)

【问题讨论】:

  • 类变量就是这样工作的。
  • @sawa:在类层次结构中,是的。但为什么在这里?

标签: ruby


【解决方案1】:

我无法弄清楚为什么 A.dynamic_set 1, 2 不起作用。

调用 setter 时使用显式接收器:

def dynamic_set(*args)
  foo += args # This doesn't work
  self.foo += args # This DOES work
end

进入问题的主要部分

TL;DR:不要使用类变量。在类级别使用实例变量。

module XYZ 
  def self.included(base)
    base.send :extend, ClassMethods
    base.class_eval do
      @foo ||= []
    end 
  end 

  module ClassMethods
    def foo 
      instance_variable_get("@foo")
    end 

    def foo=(arg)
      instance_variable_set("@foo", arg)
    end 

    def dynamic_set(*args)
      self.foo += args # This doesn't work
    end 

    def dynamic_set_2(*args)
      class_variable_set("@foo", instance_variable_get("@foo") + args)
    end 
  end 
end

值得在答案中提及。

module XYZ
  def self.included(base)
    base.class_eval { @@foo ||= [] }
  end
end

上面的代码用@@foo 污染了XYZ 类变量因为@@fooXYZ 模块中被提升

base.class_variable_set(:@@foo, []) 不会污染XYZ

【讨论】:

  • 嗨@mudasobwa - 我了解类实例变量以及为什么应该使用它。在这种情况下,我很惊讶并想知道为什么类变量会这样?类变量只能在基类及其子类中访问。
  • “类变量只能在基类及其子类中访问”——完全错误。关于它的行为方式有很多资源(大量资源)。这是 Matz 故意这样做的,我不会就为什么好人有时会做出错误的决定发表自己的看法。
  • @mudasobwa:我的意思是,这里没有类层次结构。按照设计,类变量在类及其后代中是可见的。在这里,它似乎沿着祖先链向上移动,然后向下
  • @mudasobwa 不,不是拖钓,只是咖啡不够。祖链,是的。 但是 var 是用class_eval 设置的。因此,应该拥有它的是基础,而不是模块。 ¯\_(ツ)_/¯
  • @SergioTulentsev 哈哈,这是在 XYZ.included 中提升 @@foobase.class_variable_set(:@@foo, []) 不会污染XYZ
猜你喜欢
  • 2017-07-17
  • 1970-01-01
  • 1970-01-01
  • 2011-03-17
  • 2016-09-13
  • 1970-01-01
  • 2019-04-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多