问题似乎可以归结为:鉴于以下情况:
class A
attr_reader :instance_var
def initialize
@instance_var = (@instance_var ||= 0) + 1
instance_local_var = 33
puts "instance_local_variables = #{ local_variables }"
instance_local_var = 33
end
class_local_var = 3
puts "class_local_variables = #{ local_variables }"
class_local_var = 3
end
# class_local_variables = [:class_local_var]
#=> 3
可以确定instance_local_var 和class_local_var 的值吗?
确定class_local_var的值
这个问题的答案显然是“否”,因为在end 执行后class_local_var 不再存在(已被标记为垃圾回收)1:
A.send(:local_variables)
#=> []
确定instance_local_var的值
a = A.new
# instance_local_variables = [:instance_local_var]
#=> #<A:0x007ff3ea8dbb80 @instance_var=1>
注意@instance_var #=> 1。
A.new 不会返回instance_local_var 的值,但因为该变量在initialize 的最后一行被赋值,所以可以通过再次执行initialize 来获得该值。2
instance_local_var = a.send(:initialize)
#=> 33
但是有一个问题:
a.instance_var
#=> 2
第二次执行initialize 导致了不必要的副作用。我对initialize 的定义是人为的,但它强调了这样一个事实,即第二次执行initialize 可能会出现许多不良副作用。
现在让我们获取一个新实例。
b = A.new
# instance_local_variables = [:instance_local_var]
#=> #<A:0x007fee0996e7c8 @instance_var=1>
再次,@instance_var=1。对于给定实例调用两次initialize 的副作用的一种可能解决方法是继承A 并使用super。
class B < A
attr_reader :b
def initialize
@b = super
end
end
B.new.b
#=> 33
a.instance_var
#=> 1
无法保证使用这种方法可以避免不良副作用(例如,initialize 对于任何实例都可以执行应该只发生一次的数据库操作),但它似乎离开了初始实例 a不受影响。这当然都是假设。
1.必须使用send,因为A.private_methods.include?(:local_variables) #=> true
2。 A.new.send(:initialize) 是必需的,因为 iniitialize 是私有的。