我想拒绝在Ruby中创建实例变量,以防止"错误"创建无人参与的变量。
My class:
class Test
def initialize
@a = 'Var A'
end
def make_new
@b = 'Var B' <-- I would like to deny creation of any variables that were not defined during the init
end
end
我不认为这是个好主意,但只是b/c。这有点有趣,这里有一个解决方案,它将在创建新的ivar时抛出异常,但也允许您修改已定义的实例变量(与freezing
类不同)。综合来看,它无疑存在一些问题,包括它复制了所有方法:)
module IvarBlocker
def method_added(method)
alias_name = "__#{method}_orig"
return if method == :initialize || method_defined?(alias_name) || method.match(/__.*_orig/)
alias_method alias_name, method
define_method(method) do |*args|
ivars_before = instance_variables.dup
send(alias_name, *args).tap { raise "New Ivar assigned" if !(instance_variables - ivars_before).empty? }
end
end
end
使用
class Test
extend IvarBlocker
def initialize
@a = 1
end
def set_b
@b = 2
end
def set_a
@a = 6
end
end
t = Test.new #=> #<Test:0x007f87f13c41e8 @a=1>
t.set_b #=> RuntimeError: New Ivar assigned
t.set_a #=> 6
您可以在initialize
方法结束时冻结对象实例:
class Test
def initialize
@a = 'Var A'
freeze
end
def make_new
@b = 'Var B' # I would like to deny creation of any variables that were not defined during the init
end
end
t=Test.new
p t.instance_variable_get :@a
# "Var A"
t.make_new
#a.rb:24:in `make_new': can't modify frozen Test (RuntimeError)
# from a.rb:30:in `<main>'
t.instance_variable_set :@c, 'Var C'
# a.rb:31:in `instance_variable_set': can't modify frozen Test (RuntimeError)
# from a.rb:31:in `<main>'
class << t
@d = 'Var D'
end
#a.rb:33:in `singletonclass': can't modify frozen Class (RuntimeError)
# from a.rb:32:in `<main>'
p t.instance_variable_get :@d
有一种方法——一种不适合生产(而且相对较慢)的技巧(但很有趣)的方法。我的示例实现仅适用于单个对象,但可以扩展为支持多个对象。
让我们假设以下设置:
class Foo
def initialize
@a = :foo
end
def set_b; @b = 3; end
def set_c; @c = 7; end
end
def freeze_variables_of(obj)
frozen_variables = obj.instance_variables
set_trace_func lambda {|event, file, line, id, binding, classname|
if classname == obj.class
this = binding.eval 'self'
if this == obj
(this.instance_variables - frozen_variables).each {|var| this.remove_instance_variable var}
end
end
}
end
通过使用set_trace_func
,我们可以设置一个经常调用的Proc(通常每个语句调用一次以上)。在该过程中,我们可以检查实例变量并删除不需要的变量。
让我们看一个例子:
a = Foo.new
# => #<Foo:0x007f6f9db75cc8 @a=:foo>
a.set_b; a
# => #<Foo:0x007f6f9db75cc8 @a=:foo, @b=3>
freeze_variables_of a
a.set_c; a
# => #<Foo:0x007f6f9db75cc8 @a=:foo, @b=3>
我们看到,在执行"冻结"后,set_c
无法设置实例变量(事实上,在set_c
方法返回的那一刻,变量就被删除了)。
与冻结对象(使用a.freeze
)(我建议任何现实世界的应用程序都这样做)不同,这种方式允许您修改所有允许的实例变量,同时禁止新的实例变量。
如果您直接分配实例变量,这甚至有效(而Alex的方法可能更快,但依赖于访问器方法)。
没有办法防止创建以这种方式定义的意外实例变量。你为什么要这么做?您希望这样的代码实现什么?