是否有可能比较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意味着您必须知道实例变量的名称,因此内涵类似。
对于您当前的问题,已经有好几个很好的答案,但我注意到您的代码的其他部分需要发表评论。 (但大多数都是微不足道的。)
这里有四个小问题,所有这些都与编码风格有关:
- 缩进:您将4个空格混合用于缩进和5个空格。 通常最好只坚持一种缩进方式,而在Ruby中通常只有2个空格。
- 如果方法不采用任何参数,则习惯于在方法定义中省略parantheses。
- 同样,如果您发送不带参数的消息,则保留parantheses。
- 打开paranthesis之后和关闭之前没有空格,除了块。
无论如何,这只是小事。 最重要的是:
def new @a = 1 end
这不符合你的想法! 这定义了一个名为X#new
的实例方法,而不是一个名为X.new
的类方法!
你在这里打电话:
x = X.new
是一个名为new
的类方法,它是从Class
类inheritance的。 所以,你永远不会调用你的新方法,这意味着@a = 1
永远不会被执行,这意味着@a
总是未定义,这意味着它总是会评估为nil
,这意味着self
的@a
和other
的@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