关于ruby:@ instance_variable和attr_accessor之间的区别

Difference between @instance_variable and attr_accessor

我刚开始学习Ruby,没有看到@instace_variable和使用attr_accessor声明的属性之间的区别。

以下两类的区别是什么?

1
2
3
class MyClass  
  @variable1
end

1
2
3
class MyClass
  attr_accessor :variable1
end

我在网上搜索了很多教程,每个人都使用不同的符号,这和Ruby版本有什么关系吗?我还搜索了stackoverflow中的一些旧线程

What is attr_accessor in Ruby?
What's the Difference Between These Two Ruby Class Initialization Definitions?

但我仍然无法找出什么是最好的使用方法。


实例变量在它所在的对象外部不可见;但是当您创建一个attr_accessor时,它会创建一个实例变量,并使它在对象外部可见(和可编辑)。

实例变量示例(非attr_accessor)

1
2
3
4
5
6
7
8
9
class MyClass
  def initialize
    @greeting ="hello"
  end
end

m = MyClass.new
m.greeting #results in the following error:
  #NoMethodError: undefined method `greeting' for #<MyClass:0x007f9e5109c058 @greeting="hello">

使用attr_accessor的示例:

1
2
3
4
5
6
7
8
9
10
11
class MyClass
  attr_accessor :greeting

  def initialize
    @greeting ="hello"
  end
end

m2 = MyClass.new
m2.greeting ="bonjour" # <-- set the @greeting variable from outside the object
m2.greeting #=>"bonjour"   <-- didn't blow up as attr_accessor makes the variable accessible from outside the object

希望这能说明问题。


实例变量在类外部不可见。

1
2
3
4
5
6
7
8
9
10
11
12
13
class MyClass
  def initialize
    @message ="Hello"
  end
end

msg = MyClass.new
@message
#==> nil   # This @message belongs to the global object, not msg
msg.message
#==> NoMethodError: undefined method `message'
msg.@message
#==> SyntaxError: syntax error, unexpected tIVAR

现在,您可以一直这样做:

1
msg.instance_eval { @message }

但这是尴尬和欺骗。在其他人的课堂上四处搜寻可能是有教育意义的,但是如果你想得到可靠的结果,你的客户代码不应该这样做。另一方面,如果您希望客户能够看到这些值,请不要让他们使用instance_eval;相反,请定义一个实现此技巧的方法:

1
2
3
4
5
6
7
class MyClass
  def message
    return @message
  end
end
msg.message
# ==>"Hello"

因为您经常想这样做,Ruby提供了一个快捷方式使其变得更简单。下面的代码与上面的代码具有完全相同的结果:

1
2
3
class MyClass
  attr_reader :message
end

这不是一种新的变量类型;它只是定义方法的一种简略方法。您可以查看msg.methods并看到它现在有一个message方法。

现在,如果您希望让外部人员不仅看到实例变量的值,而且还更改它,该怎么办?为此,您必须定义一个不同的分配方法,名称中包含一个=

1
2
3
4
5
6
7
8
class MyClass
  def message=(new_value)
    @message = new_value
  end
end
msg.message ="Good-bye"
msg.message
# ==>"Good-bye"

请注意,这里的赋值操作符是半魔法的;尽管msg.message=之间有一个空格,Ruby仍然知道调用message=方法。像+=等组合运算符也将触发对该方法的调用。

同样,这是一个常见的设计,所以Ruby也为它提供了一个快捷方式:

1
2
3
class MyClass
  attr_writer :message
end

现在,如果您单独使用attr_writer,您将得到一个可以修改但看不到的属性。有一些奇怪的用例是你想要的,但是大多数时候,如果你想让局外人修改变量,你也希望他们能够阅读它。不必同时申报attr_readerattr_writer,您可以同时这样申报:

1
2
3
class MyClass
  attr_accessor :message
end

同样,这只是定义方法的快捷方式,您可以从类外部获取实例变量。


attr_accesor提供了读取和写入实例变量的方法。实例变量被重新设计为隐藏在外部世界中,因此为了与它们进行通信,我们应该使用Attr聒ibute Accesor方法。


在OOPS中,我们有一个称为封装的概念,这意味着对象的内部表示通常隐藏在对象定义之外的视图中。只有对象"本身"才能与它自己的内部状态混淆。外部世界不能。

每个对象通常由其状态和行为定义,在Ruby中,实例变量称为对象的内部状态或状态,根据oops,状态不应该被任何其他对象访问,因此我们坚持封装。

例:class Foo
def initialize(bar)
@bar = bar
end
end

上面,我们定义了一个类foo,在initialize方法中,我们初始化了一个实例变量(attribute)或(property)。当我们使用新方法创建一个新的Ruby对象时,该方法反过来在内部调用initialize方法,当该方法运行时,@bar实例变量被声明并初始化,它将被保存为对象的状态。

每个实例变量都有它自己的内部状态,并且对于对象本身是唯一的,我们在类中定义的每个方法都将根据方法定义和目的改变对象的内部状态。这里,initialize方法执行相同的操作,例如创建一个新的实例变量。

1
2
var object = Foo.new(1)
#<Foo:0x00000001910cc0 @bar=1>

在后台,Ruby创建了一个实例变量(@bar=1),并将该值存储为对象"object"中对象的状态。我们可以使用"instance_variables"方法检查它,并且该方法根据对象的当前状态返回一个包含对象的所有实例变量的数组。

1
2
3
4
object.instance_variables
#[
     [0]: @bar
 ]

我们可以看到上面的"@bar"实例变量。它是在我们调用对象的初始化方法时创建的。默认情况下,此"@bar"变量不应可见(隐藏),因此其他人无法从对象外部(对象除外)从内部看到它。但是,一个对象可以处理它自己的内部状态,这意味着如果我们给它一种方法,它可以显示或更改值,这两个方法可以通过在类中创建一个新的实例方法来完成。

当我们想通过调用@bar变量来查看它时,我们会得到一个错误,默认情况下,我们看不到对象的状态。

1
2
3
4
show = object.bar
#NoMethodError: undefined method `bar' for #<Foo:0x00000001910cc0 @bar=1>
#from (irb):24
#from /home/.rvm/rubies/ruby-2.0.0-p648/bin/irb:12:in `<main>'

但是我们可以通过两种方法访问变量,这两种方法分别称为setter和getter方法,它们允许对象分别显示或更改其内部状态(实例变量/属性/属性)。

1
2
3
4
5
6
7
8
9
class Foo
  def bar
    @bar
  end

  def bar=(new_bar)
    @bar = new_bar
  end
end

我们已经定义了getter(bar)和setter(bar=)方法,我们可以以任何方式命名它们,但其中的实例变量必须与要显示或更改值的实例变量相同。setter和getter在某种程度上违反了oops概念,但它们也是非常强大的方法。

当我们通过重新打开并定义类来定义这两个方法时,当我们用这些方法调用对象时,我们可以查看实例变量(这里是@foo)并更改其值。

1
2
3
4
5
6
7
8
object.bar
1

object.bar=2
2

object.bar
2

这里我们调用了bar方法(getter),它返回@bar的值,然后我们调用了bar=method(setter),我们提供了一个新的u值作为参数,它改变了实例变量(@bar)的值,我们可以通过调用bar方法再次查看它。

在Ruby中,我们有一个名为attr_accessor的方法,它结合了setter和getter方法,我们在类内的方法定义之上定义它。attr_*方法是创建方法(setter和getter)的快捷方式。

1
2
3
class Foo
  attr_accessor :bar
end

我们必须提供一个符号(:bar)作为attr_访问器方法的参数,该方法在内部创建setter和getter方法,方法名作为提供的符号名。

如果我们只需要一个getter方法,我们可以调用attr_reader:bar如果我们只需要一个setter方法,我们可以调用attr-writer:bar

attr_访问器创建attr_writer和attr_reader方法

我们可以根据需要提供任意多的实例变量,以逗号分隔的attr_x方法

1
2
3
4
5
class Foo
  attr_writer :bar
  attr_reader :bar
  attr_accessor :bar, :baz
end


因为attr_accessor定义了方法,所以可以从类外部调用它们。@variable只能从类内访问。


另一个答案更紧凑(对于Java开发人员来说)attr_accessor :x创建@x的getter和setter。

1
2
3
class MyClassA
  attr_accessor :x
end

1
2
3
4
5
6
7
8
class MyClassB
  def x=(value) #java's typical setX(..)
    @x=value
  end
  def x
    @x
  end
end