TJC*_*ers 3 ruby class-constants
据我所知,Ruby中的"常量"按惯例称为常量,但实际上是可变的.然而,我的印象是,当他们"变异"时,有一个警告:
class Z2
M = [0,1]
end
Z2::M # => [0, 1]
Z2::M = [0,3]
(irb):warning: already initialized constant Z2::M
(irb):warning: previous definition of M was here
Run Code Online (Sandbox Code Playgroud)
但是我发现事实并非如此:
a = Z2::M
a[1] = 2
Z2::M # => [0,2] and no warning
Run Code Online (Sandbox Code Playgroud)
这是"警告"系统中的一个缺口吗?我推断一个常量的赋值会复制它,但我想这不是真的,因为看起来常量和变量指向同一个对象?这是否意味着需要冻结所有所谓的"常量"以防止它们在没有警告的情况下被更改?
猴子修补内核#warn(请参阅/sf/answers/46370551/)提出异常,您将无法阻止重新分配常量本身.在惯用的Ruby代码中,这通常不是一个实用的问题,人们期望能够执行重新打开类的操作,即使类名也是常量.
Ruby常量实际上不是不可变的,您不能冻结变量.但是,当某些内容尝试修改常量引用的冻结对象的内容时,可能会引发异常.
冻结阵列很容易:
CONSTANT_ONE = %w[one two three].freeze
Run Code Online (Sandbox Code Playgroud)
但存储在此Array中的字符串实际上是对String对象的引用.因此,虽然您无法修改此数组,但您仍然可以(例如)修改索引0引用的String对象.要解决此问题,您不仅要冻结数组,还要冻结它所拥有的对象.例如:
CONSTANT = %w[one two three].map(&:freeze).freeze
CONSTANT[2] << 'four'
# RuntimeError: can't modify frozen String
CONSTANT << 'five'
# RuntimeError: can't modify frozen Array
Run Code Online (Sandbox Code Playgroud)
由于冻结递归引用可能有点笨拙,因此很高兴知道它有一个宝石.您可以使用ice_nine来深度冻结大多数对象:
require 'ice_nine'
require 'ice_nine/core_ext/object'
OTHER_CONST = %w[a b c]
OTHER_CONST.deep_freeze
OTHER_CONST << 'd'
# RuntimeError: can't modify frozen Array
OTHER_CONST[2] = 'z'
# RuntimeError: can't modify frozen Array
Run Code Online (Sandbox Code Playgroud)
另一个需要考虑的选择是在将常量的值赋给另一个变量时调用Object#dup,例如类初始值设定项中的实例变量,以确保不会意外地改变常量的引用.例如:
class Foo
CONSTANT = 'foo'
attr_accessor :variable
def initialize
@variable = CONSTANT.dup
end
end
foo = Foo.new
foo.variable << 'bar'
#=> "foobar"
Foo::CONSTANT
#=> "foo"
Run Code Online (Sandbox Code Playgroud)