3

我想拒絕在Ruby中創建實例變量,以防止無意的變量被錯誤地創建。如何拒絕在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 

回答

3

你可以在initialize方法結束freeze對象實例:

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 
+0

這應該很容易:http://m.onkey.org/ruby-i-don-t-like-3-object-freeze –

+1

這不僅僅是防止意外創建其他實例變量。 – Max

+2

我不認爲這會很有幫助。例如:'p t.instance_variable_set:@a,7#=> RuntimeError:無法修改凍結的Test' –

1

有沒有辦法阻止定義的方式意外實例變量的創建。你爲什麼想這樣做?你想要這樣的代碼實現什麼?

+2

純粹的好奇:) –

+0

雖然你可以凍結一個類。和對象,就此而言。 –

4

我不主張這是個好主意,但只是B/C它是一種有趣的,這裏是一個解決方案,在創建新的ivar時引發異常,但也會讓您修改已定義的實例變量(不像freezing該類)。只是把這個一起,有undoubtably一些問題瓦特/它,包括它所有的複製方法:)

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 
2

有一個辦法的事實 - 哈克(但有趣)這種方式並不意味着生產(並且相對較慢)。我的示例實現僅適用於單個對象,但可以擴展爲支持許多對象。

假設如下設置:

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我們可以設置一個被稱爲very often(每條語句通常超過一次)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的方法可能更快,但依賴於存取方法),這甚至可以工作。