实例变量:self vs @

195

这里是一些代码:

class Person
  def initialize(age)
    @age = age
  end

  def age
    @age
  end

  def age_difference_with(other_person)
    (self.age - other_person.age).abs
  end

  protected :age
end

我想知道在age_difference_with方法中使用@ageself.age的区别。

7个回答

288

直接写@age访问实例变量@age。写self.age告诉对象向自己发送消息age,这通常会返回实例变量@age,但是根据给定子类中如何实现age方法,也可能执行其他任意操作。例如,你可能有一个MiddleAgedSocialite类,它报告的年龄总是比实际年龄小10岁。或更实际地说,一个PersistentPerson类可能会懒惰地从持久存储中读取数据,并将所有持久性数据缓存在哈希表中。


2
我曾经读过一本关于Rails的书,不理解self和@之间的区别,所以我应该在我的方法中始终使用self.var_name(对于那些没有setter和getter的方法),以便使用公共接口来处理我的数据。这样做比花时间在getter和setter上定义更好吧? - sarunw
2
你说的“任意数量的事情”是什么意思?我没有理解最后两个例子。 - user2167582
如果您不使用@age,直接引用age会更清晰。age可能有两种形式:(attr) age(method) age。调用self.age将调用(method) age,通常只是(attr) age,但即使存在(method) age@age也会调用(attr) age - financial_physician

24

区别在于它将方法的使用与其实现隔离开来。如果属性的实现方式发生更改,比如说保留出生日期并通过现在时间和出生日期之间的时间差计算年龄,那么依赖该方法的代码不需要更改。相反,如果直接使用该属性,则更改需要传播到代码的其他区域。从这个意义上讲,直接使用属性比使用类提供的接口更加脆弱。


19
因为 self.age 可能指代实例变量或实例方法,所以需要加以区分。 - Nolan Amy
@.@...很遗憾这种情况发生了。 - cyc115

13

第一个答案完全正确,但作为一个相对新手,我不是立即清楚它的含义(向自身发送消息?嗯...)。我认为一个简短的例子会有所帮助:

class CrazyAccessors
  def bar=(val)
    @bar = val - 20 # sets @bar to (input - 20)
  end
  def bar
    @bar
  end

  def baz=(value)
    self.bar = value # goes through `bar=` method, so @bar = (50 - 20)
  end

  def quux=(value)
    @bar = value     # sets @bar directly to 50
  end
end

obj  = CrazyAccessors.new
obj.baz = 50
obj.bar  # => 30
obj.quux = 50
obj.bar  # => 50

13
这个例子让事情变得更加混乱。 - Oskar Holmkratz
2
很抱歉,但是这个例子的注释不够详细,我无法理解你的推理过程。 - kouty
来自Smalltalk的人会说一个对象“向自己发送消息”。来自Python的人会说一个对象“调用自己的方法”。不要混淆;它们完全是相同的事情。(语义学家可能会反对,认为它们只适用于动态类型的语言,并且C++虚拟方法调用并不完全等同于发送消息。语义学家是正确的,但这可能超出了这个问题/答案的范围。) - GrandOpener
我喜欢这个例子,但请提供更多的注释以解释实际发生的情况。没有解释很难理解。 - CalamityAdam
2022年看起来对我很不错。 - financial_physician
谢谢提供示例。它展示了直接设置@bar变量(obj.quux = 50)和通过self操作符传递的区别(obj.baz = 50)。 - undefined

11

当您从Struct.new继承一个类时,请注意这是一种方便的方法来生成初始化器 (如何在Ruby中生成初始化器?)

class Node < Struct.new(:value)
    def initialize(value)
        @value = value
    end
    def show()
        p @value
        p self.value # or `p value`
    end
end 

n = Node.new(30)
n.show()

会返回

30
nil

然而,当您移除初始化器时,它将返回

nil
30

使用类定义

class Node2
    attr_accessor :value
    def initialize(value)
        @value = value
    end
    def show()
        p @value
        p self.value
    end
end

你应该提供构造函数。

n2 = Node2.new(30)
n2.show()

会返回

30
30

2
感谢@Prosseek的示例,我目前正在学习Ruby on Rails,这正是让我觉得Ruby不必要复杂的行为类型 >.<。 - cyc115

2
没有任何区别。我怀疑这只是为了看到self.age和other_person.age相邻的文档价值而做出的。
我想使用确实允许将来编写一个真正的getter,它可能会执行比仅返回实例变量更复杂的操作,在这种情况下,方法不需要改变。
但这是一个不太可能的抽象问题,毕竟,如果对象的实现发生了变化,改变其他方法是合理的,在某些时候,对象本身的一个简单引用是完全合理的。
无论如何,年龄属性的抽象仍然不能解释对self的显式使用,因为只有普通的年龄也会调用访问器。

1

self.age@age的一个getter方法。考虑以下示例:

class Person
  def initialize(age)
    @age = age
  end

  # def age
  #   @age
  # end

  def age_difference_with_1(other_person_age)
    (@age - other_person_age).abs
  end

  def age_difference_with_2(other_person_age)
    (self.age - other_person_age).abs
  end
end

person = Person.new(25)
person.age_difference_with_1(40)
# => 15
person.age_difference_with_2(40)
# NoMethodError (undefined method `age' for #<Person:0x0000000108034298 @age=25>)

注意 person.age_difference_with_2(40) 引发了 NoMethodError,因为相应的 getter 方法 age 不存在。取消注释后,此异常将消失。
  def age
    @age
  end

或者,您还可以使用以下代码行来定义一个getter方法:
  attr_reader :age

-3

@age - 是实例变量 age

self.age - 指的是实例属性 age。


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