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

时间:2011-02-18 21:34:52

标签: ruby

Ruby使用这种方便的方式通过使用

等键来共享实例变量
attr_accessor :var
attr_reader :var
attr_writer :var

如果我可以简单地使用attr_reader,为什么我会选择attr_writerattr_accessor?有没有像表演(我怀疑)?我想有一个原因,否则他们就不会制作这样的钥匙。

5 个答案:

答案 0 :(得分:721)

您可以使用不同的访问者将您的意图传达给阅读代码的人,并且无论他们如何调用公共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 _...帮助器,并且必须自己编写访问器,你会编写比你需要的更多的访问器吗?例如,如果只需要读取年龄,你还会写一个允许它被写入的方法吗?

答案 1 :(得分:22)

以上所有答案都是正确的; attr_readerattr_writer编写比手动输入它们的缩写方法更方便。除此之外,它们提供比自己编写方法定义更好的性能。有关详细信息,请参阅Aaron Patterson从this talkPDF)开始的幻灯片152。

答案 2 :(得分:15)

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

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

答案 3 :(得分:13)

您并不总是希望可以从类外部完全访问您的实例变量。在很多情况下,允许对实例变量的读访问是有意义的,但写入它可能不会(例如,从只读源检索数据的模型)。在某些情况下,你想要相反,但我想不出任何不是我头脑中的任何设计。

答案 4 :(得分:11)

重要的是要了解访问者限制对变量的访问,而不是对其内容的访问。在ruby中,与其他一些OO语言一样,每个变量都是指向实例的指针。因此,例如,如果你有一个哈希的属性,你将它设置为"只读"你总是可以改变它的内容,但不能改变指针的内容。 看看这个:

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>'

正如您所看到的那样,可以从Hash @a中删除一个键/值对,作为添加新键,更改值,eccetera。但是你不能指向一个新对象,因为它是一个只读的实例变量。