Ruby中的attr_accessor是什么?

1148
我很难理解 Ruby 中的 attr_accessor。有人能给我解释一下吗?
attr_accessor 是 Ruby 中一个用于创建getter 和 setter 方法的方法。

1
attr_accessor在Git中的工作方式是否相同?我发现有些教程解释不够详细,而其他教程则假定先前有相关知识。 - Angelfirenze
11
@Angelfirenze,gitattr_accessor 没有关系。git 是一个版本控制软件,而 attr_accessor 是 Ruby 中的一个方法。你可以在这里查看它的文档:http://ruby-doc.org/core-2.3.0/Module.html#method-i-attr_accessor。 - Uzbekjon
20个回答

2568

假设你有一个类Person

class Person
end

person = Person.new
person.name # => no method error

显然,我们从未定义name方法。让我们定义一下。

class Person
  def name
    @name # simply returning an instance variable @name
  end
end

person = Person.new
person.name # => nil
person.name = "Dennis" # => no method error

哦,我们可以读取名称,但这并不意味着我们可以分配名称。这是两种不同的方法。前者称为reader,后者称为writer。我们还没有创建writer,所以让我们创建它。

class Person
  def name
    @name
  end

  def name=(str)
    @name = str
  end
end

person = Person.new
person.name = 'Dennis'
person.name # => "Dennis"

太棒了。现在我们可以使用读取器和写入器方法来编写和读取实例变量@name。除此之外,这样做非常频繁,为什么要浪费时间每次都编写这些方法呢?我们可以更轻松地完成它。

class Person
  attr_reader :name
  attr_writer :name
end

即使如此也可能变得重复。当您需要同时使用读者和写者时,请使用访问器!

class Person
  attr_accessor :name
end

person = Person.new
person.name = "Dennis"
person.name # => "Dennis"

同样的方式!猜猜看:我们人物对象中的实例变量@name将像手动设置时一样被设置,因此您可以在其他方法中使用它。

class Person
  attr_accessor :name

  def greeting
    "Hello #{@name}"
  end
end

person = Person.new
person.name = "Dennis"
person.greeting # => "Hello Dennis"

就是这样。如果想要理解 attr_readerattr_writerattr_accessor 方法是如何为你生成方法的,请参阅其他答案、书籍和 Ruby 文档。


53
@hakunin - 谢谢你提供清晰的答案。我还不明白的是,为什么Ruby语法建议在attr_*语句中使用冒号':'来表示实例变量?似乎在类中其他地方使用相同的'@'语法引用变量更直观。 - Will
223
为了回答你的问题,你需要理解attr_accessor是一个被调用在当前类上的方法,而:name则是你传递给该方法的参数。它不是特殊的语法,而是一个简单的方法调用。如果你给它传递@name变量,那将没有意义,因为@name会包含nil。这就像写下attr_accessor nil一样。你传递的不是需要被创建的变量,而是你想要该变量被称呼的名称。 - Max Chernyak
26
@hakunin - 那非常有道理。我今天刚学习到,Ruby实际上是在解析文件时“运行”,每个语句和表达式实际上都是某个对象上的方法调用,包括attr_accessor。非常有帮助。 - Will
5
@Buminda 是的,但是 name 方法和 @name 变量不是同一个东西。不要混淆它们。在你的类中有实例变量 @name,你定义了 attr_reader :name 来能够从外部读取它。如果没有 attr_reader,你很难在类外部访问 @name - Max Chernyak
1
@MuhammadUmer 我知道这已经过时了,但我想留下这个供将来有同样疑问的人参考。它是一个冒号,因为它是一个符号,这就是 Object#send 期望接收的内容。虽然该调用可以接受一个字符串,但它必须将该字符串转换为符号。因此,通过传递一个符号,您可以节省一步。 - engineerDave
显示剩余24条评论

134

attr_accessor只是一个方法。 (链接应提供更多关于它如何工作的见解-查看生成的方法对和教程应该向您展示如何使用它。)

诀窍在于,在Ruby中,class不是一个定义(在C++和Java等语言中它是“只是一个定义”),而是一个评估表达式。在此评估过程中,会调用attr_accessor方法,从而修改当前类-请记住隐式接收者:self.attr_accessor,其中self是此时的“开放”类对象。

需要attr_accessor和相关方法的原因是:

1. Ruby和Smalltalk一样,不允许在方法之外访问实例变量1。也就是说,不能像在Java或Python中那样以x.y的形式访问实例变量。在Ruby中,y总是被视为要发送的消息(或者"要调用的方法")。因此,attr_*方法创建了包装器,通过动态创建的方法来代理实例变量@variable的访问。
2. 模板代码真烦人。
这并不完全正确,虽然有一些"技巧"可以绕过这个问题,但是没有语法支持来访问"公共实例变量"。

当你说attr_accessor只是一个方法时,我明白了。但是调用该方法所使用的语法叫什么?我在Ruby文档中找不到讨论像some_method :name => "whatever", :notherName, :etc这样的语法的部分。 - B T
当您使用“@”或“.”符号访问实例变量时,它似乎会自动调用。 - Jim Grisham

79

attr_accessor是一个方法,就像@pst所说的那样。它创建更多的方法供你使用。

因此,这段代码:

class Foo
  attr_accessor :bar
end

等同于以下代码:

class Foo
  def bar
    @bar
  end
  def bar=( new_value )
    @bar = new_value
  end
end

你可以在Ruby中自己编写这种方法:

class Module
  def var( method_name )
    inst_variable_name = "@#{method_name}".to_sym
    define_method method_name do
      instance_variable_get inst_variable_name
    end
    define_method "#{method_name}=" do |new_value|
      instance_variable_set inst_variable_name, new_value
    end
  end
end

class Foo
  var :bar
end

f = Foo.new
p f.bar     #=> nil
f.bar = 42
p f.bar     #=> 42

8
这是一个很好的例子,展示了元编程在即使是初学者级别的情境下也可以得到应用。非常不错。 - John Simon
2
我正在寻找attr_accessor的实现草图,最终在这里找到了!虽然它解决了我的问题,但我很好奇在哪里(书籍/官方文档)可以找到像这样的实现示例? - Wasif Hossain

49

attr_accessor 非常简单:

attr_accessor :foo

是以下内容的缩写:

def foo=(val)
  @foo = val
end

def foo
  @foo
end

它不过是一个对象的读取器和设置器。


11
你的回答很好。根据我的词典,“快捷方式”意味着“更短的替代路线”,而不是“语法糖”或“由解释器解释的宏”。 - bowsersenior

33

基本上,它们伪造了公开可访问的数据属性,而 Ruby 并没有这个功能。


5
尽管这条评论并不完全有用,但它是真实的。强调了在Ruby中公共数据属性不存在于“get”方法之外,对于试图学习该语言的人来说,这是非常有用的信息。 - Eric Dand
4
这个回答真的不应该被踩。作为一个非 Ruby 开发者,我正在尝试理解这些东西,这个回答非常有帮助! - Brad
1
同意,看起来非常类似于 C# 的 name {get; set;}。 - David Miler

22

这只是一种为实例变量定义getter和setter方法的方法。一个示例实现可能是:

def self.attr_accessor(*names)
  names.each do |name|
    define_method(name) {instance_variable_get("@#{name}")} # This is the getter
    define_method("#{name}=") {|arg| instance_variable_set("@#{name}", arg)} # This is the setter
  end
end

1
以这种方式处理多个属性非常棒! - Wasif Hossain
1
这是一个非常有帮助的代码片段,用于解决我在元编程方面遇到的另一个问题。 - alexventuraio

16

如果你熟悉面向对象编程的概念,你一定熟悉getter和setter方法。在Ruby中,attr_accessor做了同样的事情。

通用方式下的Getter和Setter

class Person
  def name
    @name
  end

  def name=(str)
    @name = str
  end
end

person = Person.new
person.name = 'Eshaan'
person.name # => "Eshaan"

设置方法

def name=(val)
  @name = val
end

获取方法

def name
  @name
end

Ruby中的Getter和Setter方法

class Person
  attr_accessor :name
end

person = Person.new
person.name = "Eshaan"
person.name # => "Eshaan"

2
完美的解释!这是一个非常方便的行为,也可以非常容易地被覆盖。 - Rubyrider

15

简单的解释,不涉及任何代码

大部分的回答都使用了代码,这个解释尝试通过一个比喻/故事来回答,而不使用任何代码:

外部人员无法访问内部的CIA机密

  • 让我们想象一个非常神秘的地方:中央情报局(CIA)。除了CIA内部的人员之外,没有人知道CIA内部发生了什么。换句话说,外部人员无法访问CIA中的任何信息。但是因为拥有一个完全保密的组织没有任何好处,某些信息会向外界公开 - 当然只有CIA希望每个人都知道的内容:例如CIA局长是谁、这个部门与其他政府部门相比的环保程度等等。其他信息,例如在伊拉克或阿富汗的隐蔽特工是谁,这些类型的事情可能会保持秘密150年。

  • 如果你不是CIA内部人员,你只能访问它向公众提供的信息。或者用CIA的专业术语来说,你只能访问被“批准”的信息。

  • CIA希望向外界公开的信息被称为: 属性(attributes)

读取和写入属性的含义:

  • 就CIA而言,大多数属性都是“只读”的。这意味着如果你是CIA之外的人员,你可以询问:“谁是CIA局长?”然后你会得到一个直接的答案。但是当使用“只读”属性时,你无法在CIA内部进行更改。例如,你不能打电话然后突然决定让金·卡戴珊成为局长,或者让帕丽斯·希尔顿成为总司令。

  • 如果属性赋予了你“写”访问权限,那么即使你不在CIA内部,你也可以进行更改。否则,你唯一能做的就是阅读。

  • 换句话说,访问器允许您查询或更改组织机构的内容,否则外部人员无法进入,具体取决于访问器是读访问器还是写访问器。

类中的对象可以轻松相互访问

  • 另一方面,如果您已经在CIA内部,那么您可以轻松地联系驻阿富汗的CIA特工,因为此信息可以轻松访问,只要您已经在内部。但是,如果您在CIA之外,则根本不会被授予访问权限:您将无法知道它们是谁(读访问),也无法更改其任务(写访问)。

对于类和您访问其中变量、属性和方法的能力,情况完全相同。希望有所帮助!如有任何问题,请随时提问,我希望能够澄清。


你的解释很有道理!+1 对不起,你确定“被中央情报局清除的信息是正确的”这个表达是正确的吗? - kouty
CIA 中有不同的“保密”级别,例如最高机密(只有总统可以查看),或公众信任(所有人都可以阅读信息)。CIA 实际上提供了许多非常酷的事实! - BenKoshy
你因为提到卡戴珊和帕丽斯·希尔顿的例子而值得点赞 :) 我觉得特朗普当总统已经够糟糕了,想象一下这两个人掌权,天哪! - rmcsharry
是的!这就是我们需要的,没有代码的StackOverflow! :-) - Marvin

15

我也遇到了这个问题,并对此问题写了一个相当冗长的答案。虽然已经有一些很好的答案了,但是任何需要更多澄清的人,我希望我的答案能够帮助到你。

初始化方法

初始化允许您在创建实例的同时将数据设置给对象的实例,而不必在每次创建类的新实例时在代码中单独设置它们。

class Person

  def initialize(name)
    @name = name
  end


  def greeting
    "Hello #{@name}"
  end
end

person = Person.new("Denis")
puts person.greeting
在上述代码中,我们使用初始化方法通过在 Initialize 中传递 Dennis 参数来设置名称“Denis”。如果我们想要在没有初始化方法的情况下设置名称,我们可以这样做:
class Person
  attr_accessor :name

  # def initialize(name)
  #     @name = name
  # end

  def greeting
    "Hello #{name}"
  end
end

person = Person.new
person.name = "Dennis"
puts person.greeting

在以上代码中,我们通过调用attr_accessor setter方法使用person.name设置名称,而不是在对象初始化时设置值。

这两种方法都可以工作,但是初始化可以节省我们的时间和代码行数。

这是初始化的唯一工作。您不能将其作为方法调用。要获取实例对象的值,您需要使用getter和setter(attr_reader(get),attr_writer(set)和attr_accessor(both))。有关更多详细信息,请参见下面的内容。

Getter、Setter(attr_reader、attr_writer、attr_accessor)

Getter,attr_reader:getter的整个目的是返回特定实例变量的值。请参考以下示例代码以了解详细信息。

class Item

  def initialize(item_name, quantity)
    @item_name = item_name
    @quantity = quantity
  end

  def item_name
    @item_name
  end

  def quantity
     @quantity
  end
end

example = Item.new("TV",2)
puts example.item_name
puts example.quantity
在上面的代码中,您正在对“example”项的实例调用“item_name”和“quantity”方法。“puts example.item_name”和“example.quantity”将返回(或“获取”)传递到“example”的参数的值,并将它们显示在屏幕上。在Ruby中有一个内在的方法可以让我们更简洁地编写此代码; attr_reader方法。请参见下面的代码;
class Item

attr_reader :item_name, :quantity

  def initialize(item_name, quantity)
    @item_name = item_name
    @quantity = quantity
  end

end

item = Item.new("TV",2)
puts item.item_name
puts item.quantity

这个语法的作用完全相同,只是它可以节省六行代码。如果您的项目类还有另外5个状态属性呢?那么代码很快就会变得很冗长。

Setters、attr_writer: 在使用setter方法时,最初困扰我的是,在我看来它似乎执行与initialize方法完全相同的功能。下面我将根据我的理解解释它们之间的区别;

如前所述,initialize方法允许您在对象创建时为对象实例设置值。

但是,如果您想要稍后设置这些值,或在初始化后更改它们怎么办?这就是您将使用setter方法的情况。这就是区别。在使用attr_writer方法进行初始设置时,您不必“设置”特定状态。

以下代码示例演示了使用setter方法为Item类的此实例声明item_name的值。请注意,我们继续使用getter方法attr_reader以便可以获取值并将其打印到屏幕上,以防您想要在自己的计算机上测试代码。

class Item

attr_reader :item_name

  def item_name=(str)
    @item_name = (str)
  end

end
下面的代码示例演示了如何使用attr_writer来再次缩短我们的代码并节省时间。
class Item

attr_reader :item_name
attr_writer :item_name

end

item = Item.new
puts item.item_name = "TV"
下面的代码是对上述初始化示例的重复,其中我们使用initialize在创建时设置对象的item_name值。
class Item

attr_reader :item_name

  def initialize(item_name)
    @item_name = item_name
  end

end

item = Item.new("TV")
puts item.item_name

attr_accessor:同时具有attr_reader和attr_writer的功能,可以帮助你省去一行代码。


10

我认为新的Ruby程序员(如我)感到困惑的部分是:

"为什么我不能一次性告诉实例它有任何给定的属性(例如名称),并为该属性赋值?"

更加普遍化,但这是我理解的方式:

假设:

class Person
end

我们还没有将“人”定义为可以拥有名称或其他属性的事物。

所以如果我们接着:

baby = Person.new

...并尝试为它们命名...

baby.name = "Ruth"

我们之所以会出现错误,是因为在 Rubyland 中,一个名为 Person 的对象类并不具备或关联于“名称”这个属性……还没有!

但是,我们可以使用前面提到的任何一种方法作为一种方式来表达,“Person 类的一个实例(baby现在可以拥有名为‘name’的属性,因此我们不仅有了一种语法上获取和设置该名称的方法,而且这样做也是有意义的。”

再次从稍微不同、更一般的角度回答这个问题,但我希望这能帮助下一个遇到 class Person 时的人。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接