我在想:
class X
def new()
@a = 1
end
def m( other )
@a == other.@a
end
end
x = X.new()
y = X.new()
x.m( y )
但它不起作用。
错误信息是:
syntax error, unexpected tIVAR
那么如何比较同一类的两个私有属性呢?
我在想:
class X
def new()
@a = 1
end
def m( other )
@a == other.@a
end
end
x = X.new()
y = X.new()
x.m( y )
但它不起作用。
错误信息是:
syntax error, unexpected tIVAR
那么如何比较同一类的两个私有属性呢?
对于您的直接问题,已经有几个很好的答案,但我注意到您的其他一些代码片段值得评论。(不过,它们中的大多数都是微不足道的。)
这里有四个微不足道的,它们都与编码风格有关:
无论如何,这只是小事。最重要的是:
def new
@a = 1
end
这并不像你认为的那样!这定义了一个名为的实例方法X#new
,而不是一个名为的类方法X.new
!
你在这里叫什么:
x = X.new
是一个名为 的类方法new
,它是您从Class
该类继承的。所以,你永远不会调用你的新方法,这意味着 @a = 1
永远不会被执行,这意味着@a
永远是未定义的,这意味着它总是会评估为nil
哪个意味着@a
ofself
和@a
ofother
将永远是相同的,这意味着m
永远是true
!
您可能想要做的是提供一个构造函数,但 Ruby 没有构造函数。Ruby 只使用工厂方法。
您真正想要覆盖的方法是实例方法initialize
。现在您可能会问自己:“当我实际调用一个名为的类方法时,为什么我必须重写一个实例方法?”initialize
new
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
是私有的,因此必须使用反射来规避这样的访问限制:obj.send(:initialize, *args, &block)
]
最后,让我解释一下您的m
方法出了什么问题。(其他人已经解释了如何解决它。)
在 Ruby 中,没有办法(注意:在 Ruby 中,“没有办法”实际上翻译为“总有一种方法涉及反射”)从实例外部访问实例变量。这就是为什么它毕竟被称为实例变量,因为它属于实例。这是 Smalltalk 的遗产:在 Smalltalk 中没有可见性限制,所有方法都是公开的。因此,实例变量是在 Smalltalk 中进行封装的唯一方法,而封装毕竟是 OO 的支柱之一。在 Ruby 中,存在可见性限制(例如,正如我们在上面看到的),因此没有必要因为这个原因而隐藏实例变量。然而,还有另一个原因:统一访问原则。
UAP 声明如何使用某个功能应该独立于该功能的实现方式。所以,访问一个特性应该总是相同的,即统一的。这样做的原因是该功能的作者可以自由地更改该功能在内部的工作方式,而不会破坏该功能的用户。换句话说,它是基本的模块化。
这意味着,例如,获取集合的大小应该始终相同,无论大小是否存储在变量中、每次动态计算、第一次延迟计算然后存储在变量中、memoized 等。听起来很明显,但例如 Java 会出错:
obj.size # stored in a field
对比
obj.getSize() # computed
Ruby 采取了简单的方法。在 Ruby 中,只有一种方法可以使用一个特性:发送消息。由于只有一种方式,因此访问非常统一。
因此,长话短说:您根本无法访问另一个实例的实例变量。您只能通过消息发送与该实例进行交互。这意味着另一个对象必须为您提供一种方法(在这种情况下至少是protected
可见性)来访问其实例变量,或者您必须违反该对象的封装(从而失去统一访问,增加耦合并冒着未来损坏的风险)通过使用反射(在这种情况下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 x.m(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
类使用反射版本,尽管它使用的是instance_eval
:
class X
def m(other)
@a == other.instance_eval { @a }
end
end
(我不知道为什么。可能在编写instance_variable_get
时根本不存在Set
。Ruby 将在 2 月 17 岁,stdlib 中的一些东西是从很早的时候开始的。)
有几种方法
吸气剂:
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意味着你必须知道实例变量的名字,所以内涵类似。
如果您不使用该instance_eval
选项(如@jleedev 发布的那样),并选择使用一种getter
方法,您仍然可以保留它protected
如果你想要一个protected
Ruby 中的方法,只需执行以下操作来创建一个只能从同一类的对象中读取的 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()
x.m( y ) # Returns true
x.a # 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