是否可以在 Ruby 中比较私有属性?
我心里想:
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
那么如何比较同一类的两个私有属性呢?
I'm thinking in:
class X
def new()
@a = 1
end
def m( other )
@a == other.@a
end
end
x = X.new()
y = X.new()
x.m( y )
But it doesn't works.
The error message is:
syntax error, unexpected tIVAR
How can I compare two private attributes from the same class then?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
对于您眼前的问题已经有几个很好的答案,但我注意到您的代码的其他一些部分值得评论。 (不过,大多数都是微不足道的。)
这里有四个微不足道的问题,它们都与编码风格相关:
无论如何,这只是小事。最重要的是:
这不做你认为它做的事情!这定义了一个名为
X#new
的实例方法,而不是一个名为X.new
的类方法!您在这里调用的
是一个名为
new
的 class 方法,您从Class
类继承了该方法。因此,您永远不会调用新方法,这意味着@a = 1
永远不会被执行,这意味着@a
始终未定义,这意味着它将始终评估为nil
这意味着self
的@a
和other
的@a
将始终为相同,这意味着m
将始终为true
!您可能想要做的是提供一个构造函数,但 Ruby 没有构造函数。 Ruby 仅使用工厂方法。
您真正想要重写的方法是实例方法
initialize
。现在您可能会问自己:“为什么我必须重写名为initialize
的实例方法,而实际上我正在调用名为新的
?”Ruby 中的对象构造是这样工作的:对象构造分为两个阶段,分配和初始化。分配是通过名为
allocate
的公共类方法完成的,该方法被定义为类Class
的实例方法,并且通常从不被重写。它只是为对象分配了内存空间并设置了一些指针,但是此时该对象还不能真正使用。这就是初始化器的用武之地:它是一个名为
initialize
的实例方法,它设置对象的内部状态并将其带入一致的、完全定义的状态,可供其他对象使用。所以,为了完全创建一个新对象,你需要做的是这样的:
[注意:Objective-C 程序员可能会认识到这一点。]
但是,因为太容易忘记调用
initialize
作为一般规则,对象在构造后应该完全有效,有一个名为Class#new
的便捷工厂方法,它可以为您完成所有工作,看起来像这样:[注意:实际上,
initialize
是私有的,因此必须使用反射来规避访问限制,如下所示:obj.send(:initialize, *args, &block)
]最后,让我解释一下你的
m
方法出了什么问题。 (其他人已经解释了如何解决它。)在 Ruby 中,没有办法(注意:在 Ruby 中,“没有办法”实际上翻译为“总有一种涉及反射的方法”)从实例之外。这就是为什么它被称为实例变量,因为它属于实例。这是 Smalltalk 的遗产:在 Smalltalk 中没有可见性限制,所有方法都是公共的。因此,实例变量是 Smalltalk 中唯一的封装方式,毕竟封装是 OO 的支柱之一。在 Ruby 中,存在可见性限制(例如,正如我们在上面所看到的),因此并非绝对有必要因此而隐藏实例变量。然而,还有另一个原因:统一访问原则。
UAP 规定如何使用功能应该独立于该功能实现的方式。因此,访问一个特征应该总是相同的,即统一的。原因是该功能的作者可以自由地更改该功能的内部工作方式,而不会破坏该功能的用户。换句话说,它是基本的模块化。
这意味着,例如,获取集合的大小应该始终相同,无论大小是否存储在变量中、每次动态计算、第一次延迟计算然后存储在变量中、记忆中还是其他什么。听起来很明显,但是 Java 就犯了这个错误:
而
Ruby 则采取了简单的方法。在 Ruby 中,只有一种使用功能的方式:发送消息。由于只有一种方式,因此访问非常统一。
因此,长话短说:您根本无法访问另一个实例的实例变量。您只能通过消息发送与该实例交互。这意味着另一个对象必须要么为您提供一个方法(在本例中至少是受保护的可见性)来访问其实例变量,要么您必须违反该对象的封装(从而失去 Uniform通过使用反射(在本例中为
instance_variable_get
)进行访问(增加耦合度并冒未来损坏的风险)。这就是它的全部荣耀:
或者:
我想说,您选择这两者中的哪一个取决于个人品味。标准库中的
Set
类使用反射版本,尽管 it 使用instance_eval
代替:(我不知道为什么。也许
在编写
根本不存在。到 2 月份,Ruby 就将迎来 17 岁生日了,stdlib 中的一些内容是很早的时候的。)Set
时,instance_variable_getThere have already been several good answers to your immediate problem, but I have noticed some other pieces of your code that warrant a comment. (Most of them trivial, though.)
Here's four trivial ones, all of them related to coding style:
Anyway, that's just the small stuff. The big stuff is this:
This does not do what you think it does! This defines an instance method called
X#new
and not a class method calledX.new
!What you are calling here:
is a class method called
new
, which you have inherited from theClass
class. So, you never call your new method, which means@a = 1
never gets executed, which means@a
is always undefined, which means it will always evaluate tonil
which means the@a
ofself
and the@a
ofother
will always be the same which meansm
will always betrue
!What you probably want to do is provide a constructor, except Ruby doesn't have constructors. Ruby only uses factory methods.
The method you really wanted to override is the instance method
initialize
. Now you are probably asking yourself: "why do I have to override an instance method calledinitialize
when I'm actually calling a class method callednew
?"Well, object construction in Ruby works like this: object construction is split into two phases, allocation and initialization. Allocation is done by a public class method called
allocate
, which is defined as an instance method of classClass
and is generally never overriden. It just allocates the memory space for the object and sets up a few pointers, however, the object is not really usable at this point.That's where the initializer comes in: it is an instance method called
initialize
, which sets up the object's internal state and brings it into a consistent, fully defined state which can be used by other objects.So, in order to fully create a new object, what you need to do is this:
[Note: Objective-C programmers may recognize this.]
However, because it is too easy to forget to call
initialize
and as a general rule an object should be fully valid after construction, there is a convenience factory method calledClass#new
, which does all that work for you and looks something like this:[Note: actually,
initialize
is private, so reflection has to be used to circumvent the access restrictions like this:obj.send(:initialize, *args, &block)
]Lastly, let me explain what's going wrong in your
m
method. (The others have already explained how to solve it.)In Ruby, there is no way (note: in Ruby, "there is no way" actually translates to "there is always a way involving reflection") to access an instance variable from outside the instance. That's why it's called an instance variable after all, because it belongs to the instance. This is a legacy from Smalltalk: in Smalltalk there are no visibility restrictions, all methods are public. Thus, instance variables are the only way to do encapsulation in Smalltalk, and, after all, encapsulation is one of the pillars of OO. In Ruby, there are visibility restrictions (as we have seen above, for example), so it is not strictly necessary to hide instance variables for that reason. There is another reason, however: the Uniform Access Principle.
The UAP states that how to use a feature should be independent from how the feature is implemented. So, accessing a feature should always be the same, i.e. uniform. The reason for this is that the author of the feature is free to change how the feature works internally, without breaking the users of the feature. In other words, it's basic modularity.
This means for example that getting the size of a collection should always be the same, regardless of whether the size is stored in a variable, computed dynamically every time, lazily computed the first time and then stored in a variable, memoized or whatever. Sounds obvious, but e.g. Java gets this wrong:
vs.
Ruby takes the easy way out. In Ruby, there is only one way to use a feature: sending a message. Since there is only one way, access is trivially uniform.
So, to make a long story short: you simply can't access another instance's instance variable. you can only interact with that instance via message sending. Which means that the other object has to either provide you with a method (in this case at least of
protected
visibility) to access its instance variable, or you have to violate that object's encapsulation (and thus lose Uniform Access, increase coupling and risk future breakage) by using reflection (in this caseinstance_variable_get
).Here it is, in all its glory:
Or alternatively:
Which one of those two you chose is a matter of personly taste, I would say. The
Set
class in the standard library uses the reflection version, although it usesinstance_eval
instead:(I have no idea why. Maybe
instance_variable_get
simply didn't exist whenSet
was written. Ruby is going to be 17 years old in February, some of the stuff in the stdlib is from the very early days.)Getter有以下几种方法
:
instance_eval
:instance_variable_get
:我不认为 ruby 有“friend”或“protected”访问的概念,甚至“private”也很容易到处乱砍。使用 getter 创建只读属性,instance_eval 意味着你必须知道实例变量的名称,因此内涵类似。
There are several methods
Getter:
instance_eval
:instance_variable_get
:I don't think ruby has a concept of "friend" or "protected" access, and even "private" is easily hacked around. Using a getter creates a read-only property, and instance_eval means you have to know the name of the instance variable, so the connotation is similar.
如果您不使用
instance_eval
选项(如 @jleedev 发布的那样),并选择使用getter
方法,您仍然可以对其进行保护
如果您想要 Ruby 中的
protected
方法,只需执行以下操作来创建只能从同一类的对象中读取的 getter:If you don't use the
instance_eval
option (as @jleedev posted), and choose to use agetter
method, you can still keep itprotected
If you want a
protected
method in Ruby, just do the following to create a getter that can only be read from objects of the same class:不确定,但这可能会有所帮助:
在课堂之外,这有点困难:
http:// /whynotwiki.com/Ruby_/_Variables_and_constants#Variable_scope.2Faccessibility
Not sure, but this might help:
Outside of the class, it's a little bit harder:
http://whynotwiki.com/Ruby_/_Variables_and_constants#Variable_scope.2Faccessibility