扩展初始化

时间:2015-01-07 19:43:59

标签: ruby

我正在尝试编写自定义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时,它会按预期工作。

2 个答案:

答案 0 :(得分:2)

您定义方法initialize以调用old_initialize,然后将alias_method定义为old_initialize,以便old_initializeold_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 {1}}将致电old_initialize致电old_initialize致电old_initialize致电old_initialize致电old_initialize致电old_initialize致电old_initialize 1}}将致电old_initialize致电old_initialize将致电old_initialize致电old_initialize致电old_initialize将致电old_initialize致电{{1}调用old_initialize会调用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] ...

我写了一篇相当冗长的关于&#34;权利&#34;调用旧版本的覆盖方法here

的方法

这样做的最好方法就是根本不做。不要覆盖方法,覆盖它们。 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]

我们可以通过将mixins分配给常量以使它们获得正确的名称,并修改API以便可以一次创建多个访问者来使这更好一点:

{{1}}

答案 1 :(得分: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