我在考虑:
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
我如何比较来自同一类的两个私有属性呢?
对于您当前的问题,已经有好几个很好的答案,但我注意到您的代码的其他部分需要发表评论.(但大多数都是微不足道的.)
这里有四个小问题,所有这些都与编码风格有关:
缩进:您将4个空格混合用于缩进和5个空格.通常最好只坚持一种缩进方式,而在Ruby中通常只有2个空格.
如果方法不采用任何参数,则习惯上不在方法定义中使用parantheses.
同样,如果您发送不带参数的消息,则保留parantheses.
打开paranthesis之后和结束之前没有空格,除了块.
无论如何,那只是小事.最重要的是:
def new @a = 1 end
这并不会做你认为它!这定义了一个名为的实例方法,X#new
而不是一个名为的类方法X.new
!
你在这里叫什么:
x = X.new
是一个被调用的类方法new
,它是从Class
类继承的.所以,你永远不打电话给你的新方法,这意味着 @a = 1
永远不会被执行,这意味着@a
永远是不确定的,这意味着它将始终评估为nil
这意味着@a
中self
和@a
的other
永远是这意味着同样的m
永远是true
!
你可能想要做的是提供一个构造函数,除了红宝石不具有构造函数.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将会是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意味着您必须知道实例变量的名称,因此内涵类似.