3

我想拒绝在 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
4

4 回答 4

4

我不认为这是一个好主意,但只是 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
于 2013-11-20T22:58:06.643 回答
3

您可以在方法结束时冻结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
于 2013-11-20T22:46:49.503 回答
2

有一种方法 - 一种不适合生产的 hacky(但有趣)方法(并且相对较慢)。我的示例实现仅适用于单个对象,但可以扩展为支持多个对象。

让我们假设以下设置:

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 (通常每个语句不止一次)。在那个 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 的方法可能更快,但依赖于访问器方法)。

于 2013-11-20T23:01:13.707 回答
0

没有办法防止以这种方式定义的意外实例变量的创建。你为什么要这样做?你希望这样的代码实现什么?

于 2013-11-20T22:28:56.387 回答