是否有可能比较Ruby中的私有属性?

我在考虑:

class X def new() @a = 1 end def m( other ) @a == other.@a end end x = X.new() y = X.new() xm( y ) 

但它不起作用。

错误消息是:

 syntax error, unexpected tIVAR 

我如何比较来自同一类的两个私有属性呢?

有几种方法

消气:

 class X attr_reader :a def m( other ) a == other.a end end 

instance_eval

 class X def m( other ) @a == other.instance_eval { @a } end end 

instance_variable_get

 class X def m( other ) @a == other.instance_variable_get :@a end end 

我不认为ruby有“朋友”或“受保护”访问的概念,甚至“私人”也很容易被攻击。 使用getter创建只读属性,而instance_eval意味着您必须知道实例变量的名称,因此内涵类似。

对于您当前的问题,已经有好几个很好的答案,但我注意到您的代码的其他部分需要发表评论。 (但大多数都是微不足道的。)

这里有四个小问题,所有这些都与编码风格有关:

  1. 缩进:您将4个空格混合用于缩进和5个空格。 通常最好只坚持一种缩进方式,而在Ruby中通常只有2个空格。
  2. 如果方法不采用任何参数,则习惯于在方法定义中省略parantheses。
  3. 同样,如果您发送不带参数的消息,则保留parantheses。
  4. 打开paranthesis之后和关闭之前没有空格,除了块。

无论如何,这只是小事。 最重要的是:

 def new @a = 1 end 

这不符合你的想法! 这定义了一个名为X#new实例方法,而不是一个名为X.new的类方法!

你在这里打电话:

 x = X.new 

是一个名为new方法,它是从Class类inheritance的。 所以,你永远不会调用你的新方法,这意味着@a = 1永远不会被执行,这意味着@a总是未定义,这意味着它总是会评估为nil ,这意味着self@aother@a总是是相同的,这意味着m将永远是true

你可能想要做的是提供一个构造函数,除了Ruby没有构造函数。 Ruby只使用工厂方法。

真正想要覆盖的方法是实例方法initialize 。 现在你可能会问自己:“当我实际调用一个名为new方法时,为什么我必须覆盖一个名为initialize实例方法?”

好吧,Ruby中的对象构造就像这样:对象构造分为两个阶段,即分配初始化 。 分配由名为allocate的公共类方法完成,该方法被定义为类Class的实例方法,通常不会被覆盖。 它只是为对象分配内存空间并设置了一些指针,但是,此时该对象并不真正可用。

这就是初始化器的用武之地:它是一个名为initialize的实例方法,它设置对象的内部状态并将其带入一个完全定义的一致状态,可供其他对象使用。

因此,为了完全创建一个新对象,您需要做的是:

 x = X.allocate x.initialize 

[注意:Objective-C程序员可能会认识到这一点。]

但是,因为很容易忘记调用initialize并且作为一般规则,对象在构造之后应该是完全有效的,有一个名为Class#new的便利工厂方法,它可以为您完成所有这些工作,看起来像这样:

 class Class def new(*args, &block) obj = alloc obj.initialize(*args, &block) return obj end end 

[注意:实际上, initialize是私有的,因此必须使用reflection来规避访问限制,如下所示: obj.send(:initialize, *args, &block) ]

最后,让我解释一下m方法出了什么问题。 (其他人已经解释过如何解决它。)

在Ruby中,没有办法(注意:在Ruby中,“没有办法”实际上转换为“总有一种涉及reflection的方法”)来从实例外部访问实例变量。 这就是为什么它毕竟被称为实例变量,因为它属于实例。 这是Smalltalk的遗产:在Smalltalk中没有可见性限制, 所有方法都是公开的。 因此,实例变量是在Smalltalk中进行封装的唯一方法,毕竟封装是OO的支柱之一。 在Ruby中,存在可见性限制(例如,如上所述),因此不必为此隐藏实例变量。 然而,还有另一个原因:统一访问原则。

UAP指出如何使用function应该与function的实现方式无关。 因此,访问function应始终相同,即统一。 这样做的原因是该function的作者可以自由地更改该function在内部的工作方式,而不会破坏该function的用户。 换句话说,它是基本的模块化。

这意味着,例如,获取集合的大小应始终相同,无论大小是否存储在变量中,每次动态计算,第一次懒惰计算,然后存储在变量,memoized或其他任何内容中。 听起来很明显,但是例如Java错了:

 obj.size # stored in a field 

 obj.getSize() # computed 

Ruby采取了简单的方法。 在Ruby中,只有一种方法可以使用function:发送消息。 由于只有一种方式,访问是平凡的。

因此,简而言之:您根本无法访问另一个实例的实例变量。 您只能通过邮件发送与该实例进行交互。 这意味着另一个对象必须为您提供一个方法(在这种情况下至少是protected可见性)来访问其实例变量,或者您必须违反该对象的封装(从而失去统一访问,增加耦合和风险未来破坏)通过使用reflection(在这种情况下instance_variable_get )。

在这里,它充满了荣耀:

 #!/usr/bin/env ruby class X def initialize(a=1) @a = a end def m(other) @a == other.a end protected attr_reader :a end require 'test/unit' class TestX < Test::Unit::TestCase def test_that_m_evaluates_to_true_when_passed_two_empty_xs x, y = X.new, X.new assert xm(y) end def test_that_m_evaluates_to_true_when_passed_two_xs_with_equal_attributes assert X.new('foo').m(X.new('foo')) end end 

或者:

 class X def m(other) @a == other.instance_variable_get(:@a) end end 

我会说,你选择的那两个中的哪一个是个人品味的问题。 标准库中的Set类使用reflection版本,但使用instance_eval代替:

 class X def m(other) @a == other.instance_eval { @a } end end 

(我不知道为什么。当编写Set时,也许instance_variable_get根本就不存在.Ruby在二月份将会是17岁,stdlib中的一些东西是从很早的时候开始的。)

如果你不使用instance_eval选项(如@jleedev发布的那样),并选择使用getter方法,你仍然可以protected

如果你想在Ruby中使用protected方法,只需执行以下操作来创建一个只能从同一个类的对象中读取的getter:

 class X def new() @a = 1 end def m( other ) @a == other.a end protected def a @a end end x = X.new() y = X.new() xm( y ) # Returns true xa # Throws error 

不确定,但这可能会有所帮助:

在课堂之外,它有点难:

 # Doesn't work: irb -> a.@foo SyntaxError: compile error (irb):9: syntax error, unexpected tIVAR from (irb):9 # But you can access it this way: irb -> a.instance_variable_get(:@foo) => [] 

http://whynotwiki.com/Ruby_/_Variables_and_constants#Variable_scope.2Faccessibility