2015-01-07 142 views
0

我想寫一個自定義attr_accessor擴展初始化

這也將接收一個塊,並將其結果分配給我將在以後訪問的變量,在初始化中。

class Object 
    def custom_attr_accessor(attr) 
    alias_method :old_initialize, :initialize 
    define_method "initialize" do 
     old_initialize 
     instance_variable_set "@#{attr}", yield 
    end 
    define_method "#{attr}" do 
     instance_variable_get "@#{attr}" 
    end 
    end 
end 

class Foo 
    custom_attr_accessor :foo do 
    "foo" 
    end 

    custom_attr_accessor :bar do 
    "bar" 
    end 
end 


# f = Foo.new 
# puts f.foo 
# => "foo" 
# puts f.bar 
# => "bar" 

但是我卻越來越

stack level too deep (SystemStackError)

反正上課的時候使用一個custom_attr_accessor,它按預期工作。

+1

請記住,在這種情況下' 「#{} ATTR」'相當於減少混亂'attr'。 – tadman

回答

2

您定義的方法initialize調用old_initialize,但你alias_methodold_initialize,所以old_initialize將調用old_initialize將調用old_initialize將調用old_initialize將調用old_initialize將調用old_initialize將調用old_initialize將調用old_initialize將調用old_initialize將調用old_initialize將調用old_initialize將調用old_initialize將調用old_initialize將調用old_initialize將調用old_initialize將調用old_initialize將調用old_initialize將調用old_initialize將調用old_initialize會打電話old_initialize將調用old_initialize將調用old_initialize將調用old_initialize將調用old_initialize將調用old_initialize將調用old_initialize將調用old_initialize將調用old_initialize將調用old_initialize將調用old_initialize將調用old_initialize將調用old_initialize將調用old_initialize ...

我寫了一個相當長的有關「正確」的方式稱爲舊版本的覆蓋方法的文章here

這樣做的最好方法就是根本就不這樣做。不要覆蓋方法,重寫它們。所以他們得到適當的名稱,並修改API,使人們可以一氣呵成創建多個訪問

class Module 
    def custom_attr_accessor(attr) 
    attr_reader attr 
    prepend(Module.new do 
     define_method(:initialize) do |*args| 
     super(*args) 
     instance_variable_set(:"@#{attr}", yield) 
     end 
    end) 
    end 
end 

class Foo 
    custom_attr_accessor :foo do 'foo' end 
    custom_attr_accessor :bar do 'bar' end 
end 

# It works: 
Foo.new 
# => #<Foo:0xdeadbeef081542 @foo='foo', @bar='bar'> 

# How it works: 
Foo.ancestors 
# => [#<Module:0xdeadbeef081523>, 
#  #<Module:0xdeadbeef081524>, 
#  Foo, 
#  Object, 
#  Kernel, 
#  BasicObject] 

我們可以讓這個更好一點通過分配混入常量:Ruby有繼承性,用它

class Module 
    def custom_attr_accessor(attr=(no_attr = true), **attr_specs, &blk) 
    attr_specs[attr] = blk unless no_attr 
    attr_specs.each do |attr, blk| 
     attr_reader attr 
     prepend CustomAttrAccessor.(attr, &blk) 
    end 
    end 
end 

module CustomAttrAccessor 
    def self.call(attr) 
    m = Module.new do 
     define_method(:initialize) do |*args| 
     super(*args) 
     instance_variable_set(:"@#{attr}", yield) 
     end 
    end 
    const_set(:"CustomAttrAccessor_#{attr}_#{m.object_id}", m) 
    end 
end 

class Foo 
    custom_attr_accessor :foo do 'foo' end 
    custom_attr_accessor :bar do 'bar' end 
end 

# It works: 
Foo.new 
# => #<Foo:0xdeadbeef081542 @foo='foo', @bar='bar'> 

# How it works: 
Foo.ancestors 
# => [CustomAttrAccessor::CustomAttrAccessor_bar_48151623420020, 
#  CustomAttrAccessor::CustomAttrAccessor_foo_48151623420010, 
#  Foo, 
#  Object, 
#  Kernel, 
#  BasicObject] 

class Bar 
    custom_attr_accessor :foo do 'FOO' end 
    custom_attr_accessor :bar do 'BAR' end 
    custom_attr_accessor baz: -> { 'BAZ' }, qux: -> { 'QUX' } 
end 

# It works: 
Bar.new 
# => #<Bar:0xdeadbeef081542 @foo='FOO', @bar='BAR' @baz='BAZ', @qux='QUX'> 

# How it works: 
Bar.ancestors 
# => [CustomAttrAccessor::CustomAttrAccessor_qux_48151623420060, 
#  CustomAttrAccessor::CustomAttrAccessor_baz_48151623420050, 
#  CustomAttrAccessor::CustomAttrAccessor_bar_48151623420040, 
#  CustomAttrAccessor::CustomAttrAccessor_foo_48151623420030, 
#  Bar, 
#  Object, 
#  Kernel, 
#  BasicObject] 
+0

啊,如果方法被調用一次,這將工作,但調用兩次會產生循環問題。如果'responds_to?(:old_initialize)'爲真,'alias_method'調用應該被跳過。 – tadman

+0

我已經修改了這個'alias_method:old_initialize,:initialize,除非respond_to? :old_initialize',它不工作。 – Quarktum

+0

作爲對答案的迴應,我知道發生了什麼,我不知道如何解決。除此之外,在調用初始化函數之前,我使用'alias_method' ... – Quarktum

0

你尋找的是這樣的:

class Object 
    def custom_attr_accessor(attr) 
    define_method "#{attr}=".to_sym do |val| 
     instance_variable_set("@#{attr}", val) 
    end 
    define_method attr do 
     instance_variable_get("@#{attr}") || yield 
    end 
    end 
end 

class Foo 
    custom_attr_accessor :foo do 
    "foo" 
    end 

    custom_attr_accessor :bar do 
    "bar" 
    end 
end 


f = Foo.new 
puts f.foo #=> foo 
f.foo = 1 
puts f.foo #=> 1 

puts f.bar #=> bar 
f.bar = 2 
puts f.bar #=> 2 
+0

不,這將在您調用方法時第一次調用該塊,而不是在初始化時調用該塊。 – Quarktum

+0

你可以定義什麼'custom_attr_accessor'應該定義? – knut