Osc*_*Ryz 3 ruby attributes private instance-variables
我在考虑:
class X
def new()
@a = 1
end
def m( other )
@a == other.@a
end
end
x = X.new()
y = X.new()
x.m( y )
Run Code Online (Sandbox Code Playgroud)
但它不起作用.
错误消息是:
syntax error, unexpected tIVAR
Run Code Online (Sandbox Code Playgroud)
我如何比较来自同一类的两个私有属性呢?
Jör*_*tag 11
对于您当前的问题,已经有好几个很好的答案,但我注意到您的代码的其他部分需要发表评论.(但大多数都是微不足道的.)
这里有四个小问题,所有这些都与编码风格有关:
无论如何,那只是小事.最重要的是:
def new
@a = 1
end
Run Code Online (Sandbox Code Playgroud)
这并不会做你认为它!这定义了一个名为的实例方法,X#new而不是一个名为的类方法X.new!
你在这里叫什么:
x = X.new
Run Code Online (Sandbox Code Playgroud)
是一个被调用的类方法new,它是从Class类继承的.所以,你永远不打电话给你的新方法,这意味着 @a = 1永远不会被执行,这意味着@a永远是不确定的,这意味着它将始终评估为nil这意味着@a中self和@a的other永远是这意味着同样的m永远是true!
你可能想要做的是提供一个构造函数,除了红宝石不具有构造函数.Ruby只使用工厂方法.
您真正想要覆盖的方法是实例方法initialize.现在你可能会问自己:"为什么我必须覆盖当我实际调用类方法时调用的实例方法?"initializenew
好吧,Ruby中的对象构造就像这样:对象构造分为两个阶段,即分配和初始化.分配由一个名为的公共类方法完成,该方法allocate被定义为类的实例方法,Class通常不会被覆盖.它只是为对象分配内存空间并设置几个指针,但是,此时该对象并不真正可用.
这就是初始化程序的用武之地:它是一个名为的实例方法initialize,它设置对象的内部状态并将其置于一个完全定义的一致状态,可供其他对象使用.
所以,为了完全创建一个新对象,你需要做的是:
x = X.allocate
x.initialize
Run Code Online (Sandbox Code Playgroud)
[注意:Objective-C程序员可能会认识到这一点.]
但是,因为它太容易忘记调用,initialize并且作为一般规则,对象在构造之后应该是完全有效的,所以有一个方便的工厂方法调用Class#new,它可以为你完成所有工作,看起来像这样:
class Class
def new(*args, &block)
obj = alloc
obj.initialize(*args, &block)
return obj
end
end
Run Code Online (Sandbox Code Playgroud)
[注意:实际上,它initialize是私有的,因此必须使用反射来规避这样的访问限制:obj.send(:initialize, *args, &block)]
最后,让我解释一下你的m方法出了什么问题.(其他人已经解释过如何解决它.)
在Ruby中,没有办法(注意:在Ruby中,"没有办法"实际上转换为"总有一种涉及反射的方法")来从实例外部访问实例变量.这就是为什么它毕竟被称为实例变量,因为它属于实例.这是Smalltalk的遗产:在Smalltalk中没有可见性限制,所有方法都是公开的.因此,实例变量是在Smalltalk中进行封装的唯一方法,毕竟封装是OO的支柱之一.在Ruby中,存在有知名度的限制(如我们上面看到的,例如),所以它不是绝对必要隐藏这个原因实例变量.然而,还有另一个原因:统一访问原则.
UAP指出如何使用功能应该与功能的实现方式无关.因此,访问功能应始终相同,即统一.这样做的原因是该功能的作者可以自由更改该功能在内部的工作方式,而不会破坏该功能的用户.换句话说,它是基本的模块化.
这意味着,例如,获取集合的大小应始终相同,无论大小是否存储在变量中,每次动态计算,第一次懒惰计算,然后存储在变量,memoized或其他任何内容中.听起来很明显,但是例如Java错了:
obj.size # stored in a field
Run Code Online (Sandbox Code Playgroud)
与
obj.getSize() # computed
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
或者:
class X
def m(other)
@a == other.instance_variable_get(:@a)
end
end
Run Code Online (Sandbox Code Playgroud)
我会说,你选择的那两个中的哪一个是个人品味的问题.Set标准库中的类使用反射版本,但它使用的是instance_eval:
class X
def m(other)
@a == other.instance_eval { @a }
end
end
Run Code Online (Sandbox Code Playgroud)
(我不明白为什么.也许instance_variable_get只是在Set写的时候根本不存在.二月份的Ruby将会是17岁,stdlib中的一些东西是从很早的时候开始的.)
有几种方法
消气:
class X
attr_reader :a
def m( other )
a == other.a
end
end
Run Code Online (Sandbox Code Playgroud)
instance_eval:
class X
def m( other )
@a == other.instance_eval { @a }
end
end
Run Code Online (Sandbox Code Playgroud)
instance_variable_get:
class X
def m( other )
@a == other.instance_variable_get :@a
end
end
Run Code Online (Sandbox Code Playgroud)
我不认为ruby有"朋友"或"受保护"访问的概念,甚至"私人"也很容易被攻击.使用getter创建只读属性,而instance_eval意味着您必须知道实例变量的名称,因此内涵类似.