Ruby:为内置类添加自定义属性

时间:2010-03-08 02:13:49

标签: ruby properties customization monkeypatching

问题: 使用Ruby可以很容易地将自定义方法添加到现有类中,但是如何添加自定义属性?这是我想要做的一个例子:

myarray = Array.new();
myarray.concat([1,2,3]);
myarray._meta_ = Hash.new();      # obviously, this wont work
myarray._meta_['createdby'] = 'dreftymac';
myarray._meta_['lastupdate'] = '1993-12-12';

## desired result
puts myarray._meta_['createdby']; #=> 'dreftymac'
puts myarray.inspect()            #=> [1,2,3]

目标是以这样的方式构造类定义,使得在上面的示例中不起作用的东西将按预期工作。

更新(澄清问题)原始问题中遗漏了一个方面:添加“默认值”也是一个目标,通常会在中设置初始化类的方法。

更新:(为什么这样做)通常,创建一个继承自Array(或任何你想要模拟的内置类)的自定义类非常简单。这个问题源自一些“仅测试”代码,并不是试图忽略这种普遍接受的方法。

2 个答案:

答案 0 :(得分:2)

财产不仅仅是一个吸气者和一个二传手吗?如果是这样,你不能这样做:

class Array
  # Define the setter
  def _meta_=(value)
    @_meta_ = value
  end

  # Define the getter
  def _meta_
    @_meta_
  end
end

然后,你可以这样做:

x = Array.new
x._meta_
# => nil

x._meta_ = {:name => 'Bob'}

x._meta_
# => {:name => 'Bob'}

这有帮助吗?

答案 1 :(得分:1)

回想一下,在Ruby中,您无权访问该实例之外的属性(实例变量)。您只能访问实例的公共方法。

您可以使用attr_accessor为您描述的充当属性的类创建方法:

irb(main):001:0> class Array
irb(main):002:1>  attr_accessor :_meta_
irb(main):003:1> end
=> nil
irb(main):004:0> 
irb(main):005:0* x = [1,2,3]
=> [1, 2, 3]
irb(main):006:0> x._meta_ = Hash.new
=> {}
irb(main):007:0> x._meta_[:key] = 'value'
=> "value"
irb(main):008:0> 

对于一个为访问者进行默认初始化的简单方法,我们基本上需要reimplement attr_accessor ourselves

class Class
  def attr_accessor_with_default accessor, default_value
    define_method(accessor) do
      name = "@#{accessor}"
      instance_variable_set(name, default_value) unless instance_variable_defined?(name)
      instance_variable_get(name)
    end

    define_method("#{accessor}=") do |val|
      instance_variable_set("@#{accessor}", val)
    end
  end
end

class Array
    attr_accessor_with_default :_meta_, {}
end

x = [1,2,3]
x._meta_[:key] = 'value'
p x._meta_

y = [4,5,6]
y._meta_[:foo] = 'bar'
p y._meta_

但是等等!输出不正确:

{:key=>"value"}
{:foo=>"bar", :key=>"value"}

我们围绕文字哈希的默认值创建了一个闭包。

更好的方法可能是简单地使用一个块:

class Class
  def attr_accessor_with_default accessor, &default_value_block
    define_method(accessor) do
      name = "@#{accessor}"
      instance_variable_set(name, default_value_block.call) unless instance_variable_defined?(name)
      instance_variable_get(name)
    end

    define_method("#{accessor}=") do |val|
      instance_variable_set("@#{accessor}", val)
    end
  end
end

class Array
    attr_accessor_with_default :_meta_ do Hash.new end
end

x = [1,2,3]
x._meta_[:key] = 'value'
p x._meta_

y = [4,5,6]
y._meta_[:foo] = 'bar'
p y._meta_

现在输出是正确的,因为每次检索默认值时都会调用Hash.new,而不是每次都重复使用相同的文字哈希。

{:key=>"value"}
{:foo=>"bar"}