为什么要使用Ruby的attr_accessor,attr_reader和attr_writer?


517

Ruby具有这种便捷的方式,可以通过使用诸如

attr_accessor :var
attr_reader :var
attr_writer :var

我为什么要选择attr_reader或者attr_writer如果我可以简单地使用attr_accessor呢?是否有类似性能的东西(我对此表示怀疑)?我想这是有原因的,否则他们就不会做出这样的钥匙。


Answers:


746

您可以使用不同的访问器将您的意图传达给阅读您的代码的人,并且使编写类的工作变得更容易,无论如何调用他们的公共API都可以正常工作。

class Person
  attr_accessor :age
  ...
end

在这里,我可以看到我可以读写年龄。

class Person
  attr_reader :age
  ...
end

在这里,我可以看到我只能读年龄。想象一下,它是由此类的构造函数设置的,之后保持不变。如果存在年龄的转换器(编写器),并且假定年龄(一旦设定)不变,则编写类,则调用该转换器的代码可能会导致错误。

但是幕后发生了什么?

如果您写:

attr_writer :age

转换为:

def age=(value)
  @age = value
end

如果您写:

attr_reader :age

转换为:

def age
  @age
end

如果您写:

attr_accessor :age

转换为:

def age=(value)
  @age = value
end

def age
  @age
end

知道了这一点,这是另一种思考方式:如果您没有attr _...助手,而不得不自己编写访问器,那么您编写的访问器是否会超出您的类所需?例如,如果仅需要读取年龄,您还会编写一种允许写入的方法吗?


53
confreaks.net/videos/…相比,写作还有明显的性能优势。attr_reader :adef a; return a; end
Nitrodist 2012年

83
@Nitrodist,有趣。对于Ruby 1.8.7,attr_reader定义的accesor占用了手动定义的访问器的86%的时间。对于Ruby 1.9.0,attr_reader定义的访问器花费的时间是手动定义的访问器的94%。但是,在我所有的测试中,访问器都非常快:访问器大约需要820纳秒(Ruby 1.8.7)或440纳秒(Ruby 1.9)。以这种速度,您需要调用访问器数亿次,attr_accessor以提高性能,甚至将整体运行时间缩短一秒钟。
韦恩·康拉德

22
“大概是由此类的构造函数设置的,并且保持不变。” 那是不准确的。具有读取器的实例变量可能会经常更改。但是,仅打算由类私下更改其值。
mlibby 2012年

11
您可以使用“,”添加两个以上的属性,例如:attr_accessor :a, :b
Andrew_1510 2014年

2
这些年来的价值:github.com/JuanitoFatas/…根据ruby 2.2.0的最新基准,attr_ *比getter和setter更快。
molli

25

以上所有答案都是正确的;attr_reader并且attr_writer比手动键入速记方法更方便编写。除此之外,它们提供的性能要比自己编写方法定义好得多。有关更多信息,请参阅亚伦·帕特森(Aaron Patterson)的演讲PDF)以后的幻灯片152 。


16

并非对象的所有属性都应从类外部直接设置。通常,为所有实例变量配备编写器通常是弱封装的标志,并且警告您在类之间引入过多耦合。

作为一个实际的例子:我编写了一个设计程序,将物品放入容器中。该项目具有attr_reader :container,但提供一个编写者没有任何意义,因为该项目的容器唯一应该改变的时间是将其放置在新的容器中,这也需要定位信息。


16

重要的是要了解访问器限制对变量的访问,但不限制对变量内容的访问。与其他OO语言一样,在ruby中,每个变量都是指向实例的指针。因此,例如,如果您具有哈希的属性,并且将其设置为“只读”,则始终可以更改其内容,但不能更改指针的内容。看这个:

irb(main):024:0> class A
irb(main):025:1> attr_reader :a
irb(main):026:1> def initialize
irb(main):027:2> @a = {a:1, b:2}
irb(main):028:2> end
irb(main):029:1> end
=> :initialize
irb(main):030:0> a = A.new
=> #<A:0x007ffc5a10fe88 @a={:a=>1, :b=>2}>
irb(main):031:0> a.a
=> {:a=>1, :b=>2}
irb(main):032:0> a.a.delete(:b)
=> 2
irb(main):033:0> a.a
=> {:a=>1}
irb(main):034:0> a.a = {}
NoMethodError: undefined method `a=' for #<A:0x007ffc5a10fe88 @a={:a=>1}>
        from (irb):34
        from /usr/local/bin/irb:11:in `<main>'

如您所见,有可能从哈希@a中删除键/值对,例如添加新键,更改值等。但是您不能指向新对象,因为它是只读实例变量。


13

您并不总是希望实例变量可以从类外部完全访问。在很多情况下,允许对实例变量进行读取访问是有道理的,但对它的写入可能就没有意义(例如,从只读源检索数据的模型)。在某些情况下,您想要相反的选择,但我想不出没有想到的事情。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.