我很难理解 Ruby 中的attr_accessor
。
有人可以向我解释一下吗?
假设您有一个类 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
啊哈,我们可以读取名称,但这并不意味着我们可以指定名称。这是两种不同的方法。前者称为读者,后者称为作家。我们还没有创建作家,所以让我们这样做。
class Person
def name
@name
end
def name=(str)
@name = str
end
end
person = Person.new
person.name = 'Dennis'
person.name # => "Dennis"
惊人的。现在我们可以使用 reader 和 writer 方法写入和读取实例变量 @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"
以同样的方式工作!猜猜看:我们的 person 对象中的实例变量 @name
将像我们手动设置时一样设置,因此您可以在其他方法中使用它。
class Person
attr_accessor :name
def greeting
"Hello #{@name}"
end
end
person = Person.new
person.name = "Dennis"
person.greeting # => "Hello Dennis"
而已。要了解 attr_reader
、attr_writer
和 attr_accessor
方法实际上如何为您生成方法,请阅读其他答案、书籍、ruby 文档。
attr_accessor 只是一种方法。 (该链接应提供有关其工作原理的更多见解 - 查看生成的方法对,教程应向您展示如何使用它。)
诀窍在于 class
在 Ruby 中不是一个定义(它在 C++ 和 Java 等语言中“只是一个定义”),但它是一个评估的表达式 .正是在此评估期间,调用 attr_accessor
方法进而修改当前类 - 请记住隐式接收器:self.attr_accessor
,其中 self
是此时的“打开”类对象。
attr_accessor
和朋友的需要是,嗯:
与 Smalltalk 一样,Ruby 不允许在该对象的方法 1 之外访问实例变量。也就是说,不能以 xy 形式访问实例变量,这在 Java 甚至 Python 中很常见。在 Ruby 中,y 始终被视为要发送的消息(或“调用方法”)。因此 attr_* 方法创建包装器,通过动态创建的方法代理实例 @variable 访问。样板很烂
希望这能澄清一些小细节。快乐编码。
1 这并不完全正确,有 some "techniques" around this,但不支持“公共实例变量”访问的语法。
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
attr_accessor
的实现草图,终于在这里找到了!虽然它解决了我的问题,但我很想知道在哪里(书籍/官方文档)可以找到这样的实现示例?
attr_accessor
非常简单:
attr_accessor :foo
是一个快捷方式:
def foo=(val)
@foo = val
end
def foo
@foo
end
它只不过是一个对象的 getter/setter
基本上,他们伪造了 Ruby 所没有的可公开访问的数据属性。
它只是一个为实例变量定义 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
如果您熟悉 OOP 概念,则必须熟悉 getter 和 setter 方法。 attr_accessor 在 Ruby 中也是如此。
一般方式的 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"
简单解释,无需任何代码
上面的大多数答案都使用代码。这个解释试图通过一个类比/故事来回答它而不使用任何东西:
外部各方无法访问 CIA 内部机密
让我们想象一个真正秘密的地方:中央情报局。除了中央情报局内部的人之外,没有人知道中央情报局发生了什么。换句话说,外部人员无法访问 CIA 中的任何信息。但是因为拥有一个完全保密的组织是不好的,所以某些信息会向外界公开——只有中央情报局当然希望每个人都知道的事情:例如中央情报局局长,这个部门的环境友好程度如何所有其他政府部门等其他信息:例如,谁是其在伊拉克或阿富汗的秘密特工——这些类型的事情可能在未来 150 年内仍然是一个秘密。
如果您在 CIA 之外,您只能访问它向公众提供的信息。或者使用 CIA 的说法,您只能访问“清除”的信息。
CIA 想要向 CIA 以外的公众提供的信息称为:属性。
读写属性的含义:
在 CIA 的情况下,大多数属性是“只读的”。这意味着如果你是 CIA 以外的一方,你可以问:“谁是 CIA 的局长?”你会得到一个直接的答案。但是你不能用“只读”属性做的是在 CIA 中进行更改。例如,您不能在打完电话后突然决定要让金·卡戴珊担任导演,或者要让帕丽斯·希尔顿担任总司令。
如果属性为您提供“写入”访问权限,那么您可以根据需要进行更改,即使您在外面也是如此。否则,你唯一能做的就是阅读。换句话说,访问者允许您对不允许外部人员进入的组织进行查询或更改,具体取决于访问者是读取访问者还是写入访问者。
类内的对象可以很容易地相互访问
另一方面,如果您已经在中央情报局内部,那么您可以轻松地致电您在喀布尔的中央情报局特工,因为您已经在内部,因此很容易获得此信息。但是,如果您在 CIA 之外,您将无法获得访问权限:您将无法知道他们是谁(读取权限),并且您将无法更改他们的任务(写入权限)。
与类以及访问其中的变量、属性和方法的能力完全相同。 !任何问题,请询问,我希望我能澄清。
我也遇到了这个问题,并为这个问题写了一个有点冗长的答案。已经有一些很好的答案,但是任何人都在寻找更多的澄清,我希望我的回答能有所帮助
初始化方法
Initialize 允许您在创建实例时将数据设置为对象的实例,而不必在每次创建类的新实例时将它们设置在代码中的单独行上。
class Person
def initialize(name)
@name = name
end
def greeting
"Hello #{@name}"
end
end
person = Person.new("Denis")
puts person.greeting
在上面的代码中,我们通过将 Dennis 传递给 Initialize 中的参数,使用 initialize 方法设置名称“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
在上面的代码中,我们通过使用 person.name 调用 attr_accessor setter 方法来设置名称,而不是在初始化对象时设置值。
完成这项工作的两种“方法”,但初始化为我们节省了时间和代码行数。
这是初始化的唯一工作。您不能将初始化作为方法调用。要实际获取实例对象的值,您需要使用 getter 和 setter(attr_reader (get)、attr_writer(set) 和 attr_accessor(both))。有关这些的更多详细信息,请参见下文。
获取器、设置器(attr_reader、attr_writer、attr_accessor)
Getters, 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
在上面的代码中,您在 Item “example” 的实例上调用方法 “item_name” 和 “quantity”。 “puts example.item_name”和“example.quantity”将返回(或“get”)传递给“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 个可归因于 Item 类的状态?代码会很快变长。
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 的功能,为您节省多行代码。
我认为让新的 Rubyists/程序员(比如我自己)感到困惑的部分原因是:
“为什么我不能告诉实例它具有任何给定的属性(例如,名称)并一举给该属性一个值?”
更笼统一点,但这就是我点击的方式:
鉴于:
class Person
end
我们还没有将 Person 定义为可以具有名称或任何其他属性的东西。
所以如果我们那么:
baby = Person.new
...并尝试给他们一个名字...
baby.name = "Ruth"
我们得到一个错误,因为在 Rubyland 中,Person 对象类不是与“名称”相关联或能够具有“名称”的东西……但是!
但是我们可以使用任何给定的方法(参见前面的答案)来表示,“Person 类 (baby
) 的实例现在可以有一个名为‘name’的属性,因此我们不仅有一种获取和设置该名称的语法方式,而且这样做对我们来说是有意义的。”
再次,从一个稍微不同和更一般的角度来回答这个问题,但我希望这有助于下一个 Person 类的实例,他们找到了进入这个线程的方式。
简单地说,它将为该类定义一个 setter 和 getter。
注意
attr_reader :v is equivalant to
def v
@v
end
attr_writer :v is equivalant to
def v=(value)
@v=value
end
所以
attr_accessor :v which means
attr_reader :v; attr_writer :v
等价于为类定义 setter 和 getter。
只需 attr-accessor
为指定属性创建 getter
和 setter
方法
另一种理解它的方法是找出它通过使用 attr_accessor
消除了哪些错误代码。
例子:
class BankAccount
def initialize( account_owner )
@owner = account_owner
@balance = 0
end
def deposit( amount )
@balance = @balance + amount
end
def withdraw( amount )
@balance = @balance - amount
end
end
可以使用以下方法:
$ bankie = BankAccout.new("Iggy")
$ bankie
$ bankie.deposit(100)
$ bankie.withdraw(5)
以下方法会引发错误:
$ bankie.owner #undefined method `owner'...
$ bankie.balance #undefined method `balance'...
owner
和 balance
在技术上不是一种方法,而是一个属性。 BankAccount 类没有 def owner
和 def balance
。如果是这样,那么您可以使用下面的两个命令。但这两种方法都不存在。但是,您可以访问 属性,就像您通过 attr_accessor
访问方法一样访问!! 因此是 attr_accessor
这个词。属性。存取器。它像访问方法一样访问属性。
添加 attr_accessor :balance, :owner
允许您读写 balance
和 owner
“方法”。现在您可以使用最后两种方法。
$ bankie.balance
$ bankie.owner
为此模块定义一个命名属性,其中名称为 symbol.id2name,创建一个实例变量(@name)和一个相应的访问方法来读取它。还创建了一个名为 name= 的方法来设置属性。
module Mod
attr_accessor(:one, :two)
end
Mod.instance_methods.sort #=> [:one, :one=, :two, :two=]
总结一个属性访问器又名 attr_accessor 给你两个免费的方法。
就像在 Java 中一样,它们被称为 getter 和 setter。
许多答案都展示了很好的例子,所以我只是简短的。
#the_attribute
和
#the_attribute=
在旧的 ruby 文档中,井号标签 # 表示方法。它还可以包括类名前缀... MyClass#my_method
我是 ruby 的新手,只需要处理理解以下奇怪之处。将来可能会帮助其他人。最后就像上面提到的那样,两个函数(def myvar,def myvar=)都隐式地访问@myvar,但是这些方法可以被本地声明覆盖。
class Foo
attr_accessor 'myvar'
def initialize
@myvar = "A"
myvar = "B"
puts @myvar # A
puts myvar # B - myvar declared above overrides myvar method
end
def test
puts @myvar # A
puts myvar # A - coming from myvar accessor
myvar = "C" # local myvar overrides accessor
puts @myvar # A
puts myvar # C
send "myvar=", "E" # not running "myvar =", but instead calls setter for @myvar
puts @myvar # E
puts myvar # C
end
end
属性和访问器方法
属性是可以从对象外部访问的类组件。它们在许多其他编程语言中被称为属性。它们的值可以通过使用“点符号”来访问,如 object_name.attribute_name。与 Python 和其他一些语言不同,Ruby 不允许直接从对象外部访问实例变量。
class Car
def initialize
@wheels = 4 # This is an instance variable
end
end
c = Car.new
c.wheels # Output: NoMethodError: undefined method `wheels' for #<Car:0x00000000d43500>
在上面的例子中,c 是 Car 类的一个实例(对象)。我们尝试从对象外部读取wheels 实例变量的值,但没有成功。发生的事情是 Ruby 试图在 c 对象中调用一个名为 wheel 的方法,但没有定义这样的方法。简而言之,object_name.attribute_name 尝试在对象中调用名为 attribute_name 的方法。要从外部访问wheels 变量的值,我们需要使用该名称实现一个实例方法,该方法将在调用时返回该变量的值。这称为访问器方法。在一般编程上下文中,从对象外部访问实例变量的常用方法是实现访问器方法,也称为 getter 和 setter 方法。 getter 允许从外部读取类中定义的变量的值,而 setter 允许从外部写入变量的值。
在下面的示例中,我们向 Car 类添加了 getter 和 setter 方法,以从对象外部访问wheels 变量。这不是定义 getter 和 setter 的“Ruby 方式”;它仅用于说明 getter 和 setter 方法的作用。
class Car
def wheels # getter method
@wheels
end
def wheels=(val) # setter method
@wheels = val
end
end
f = Car.new
f.wheels = 4 # The setter method was invoked
f.wheels # The getter method was invoked
# Output: => 4
上面的示例有效,类似的代码通常用于在其他语言中创建 getter 和 setter 方法。但是,Ruby 提供了一种更简单的方法来执行此操作:三个称为 attr_reader、attr_writer 和 attr_accessor 的内置方法。 attr_reader 方法使实例变量从外部可读,attr_writer 使其可写,attr_accessor 使其可读可写。
上面的例子可以这样改写。
class Car
attr_accessor :wheels
end
f = Car.new
f.wheels = 4
f.wheels # Output: => 4
在上面的示例中,wheels 属性将从对象外部读取和写入。如果我们使用 attr_reader 而不是 attr_accessor,它将是只读的。如果我们使用 attr_writer,它将是只写的。这三个方法本身不是 getter 和 setter,但是当被调用时,它们会为我们创建 getter 和 setter 方法。它们是动态(以编程方式)生成其他方法的方法;这就是所谓的元编程。
第一个(较长的)示例没有使用 Ruby 的内置方法,只有在 getter 和 setter 方法中需要额外代码时才应该使用。例如,setter 方法可能需要在将值分配给实例变量之前验证数据或进行一些计算。
通过使用 instance_variable_get 和 instance_variable_set 内置方法,可以从对象外部访问(读取和写入)实例变量。然而,这很少是合理的,而且通常是一个坏主意,因为绕过封装往往会造成各种破坏。
尽管存在大量现有答案,但在我看来,它们似乎都无法解释此处涉及的实际机制。这是元编程;它利用了以下两个事实:
您可以即时修改模块/类
模块/类声明本身就是可执行代码
好吧,想象一下:
class Nameable
def self.named(whatvalue)
define_method :name do whatvalue end
end
end
我们声明了一个类方法 named
,当用一个值调用它时,创建一个实例方法,称为name
,它返回那个值。那是元编程部分。
现在我们将继承该类:
class Dog < Nameable
named "Fido"
end
我们到底做了什么?好吧,在类声明中,可执行代码参照类执行。因此,裸词 named
实际上是对类方法 named
的调用,它是我们从 Nameable 继承的;我们将字符串 "Fido"
作为参数传递。
类方法 named
做了什么?它创建一个名为 name
的实例方法,该方法返回该值。所以现在,在幕后,Dog 有一个看起来像这样的方法:
def name
"Fido"
end
不相信我?然后看这个小动作:
puts Dog.new.name #=> Fido
为什么我告诉你这一切?因为我刚刚为 Nameable 使用 named
所做的几乎正是 attr_accessor
为 Module 所做的。当您说 attr_accessor
时,您正在调用创建实例方法的类方法(继承自 Module)。特别是,它为您提供其名称作为参数的实例属性创建了一个 getter 和 setter 方法,因此您不必自己编写这些 getter 和 setter 方法。
嗯。很多很好的答案。这是我的几分钱。
attr_accessor 是一个简单的方法,可以帮助我们清理(DRY-ing)重复的 getter 和 setter 方法。
这样我们就可以更专注于编写业务逻辑,而不用担心 setter 和 getter。
attr_accessor 优于其他的主要功能是能够从其他文件访问数据。因此,您通常会使用 attr_reader 或 attr_writer,但好消息是 Ruby 允许您将这两者与 attr_accessor 结合在一起。我认为它是我的方法,因为它更全面或更通用。另外,请记住,在 Rails 中,这已被消除,因为它会在后端为您完成。所以换句话说:你最好使用 attr_accessor 而不是其他两个,因为你不必担心具体,访问器涵盖了所有内容。我知道这更像是一个一般性的解释,但它帮助了我作为初学者。
希望这有帮助!
不定期副业成功案例分享
attr_accessor
是在当前类上调用的方法,而:name
是您传递给该方法的参数。这不是特殊的语法,它是一个简单的方法调用。如果你要给它@name
变量,那就没有意义了,因为@name 将包含nil
。所以这就像写attr_accessor nil
。您没有传递它需要创建的变量,而是传递了您希望调用该变量的名称。name
和变量@name
不是一回事。不要混淆他们。您的类中有实例变量@name
,并且您定义attr_reader :name
以便能够从外部读取它。如果没有attr_reader
,您就无法在课堂外访问@name
。