这与平等没有任何关系。这只是面向对象编程的基础。
在 OOP 中,所有计算都是通过对象向其他对象发送消息来完成的。OOP的基本属性之一是接收者对象并且只有接收者对象决定如何响应此消息。这就是在 OOP 中实现封装、数据隐藏和抽象的方式。
因此,如果您将消息发送到作为参数传递的m
对象,则可以决定如何解释此消息。如果您将消息发送到作为参数传递的对象,则由它来决定如何解释此消息。没有内置机制可以保证和解释此消息相同。只有当两个对象决定相互协调时,响应才会真正相同。a
b
a
m
b
a
b
a
b
如果你想一想,如果2 - 3
和3 - 2
有相同的结果,那将是非常奇怪的。
这正是这里发生的事情:在第一个示例中,您将消息发送==
到,作为参数my_pw
传递。是class的一个实例,因此消息将被分派给该方法。此方法知道如何将接收者对象与另一个对象进行比较。但是,它确实不知道如何将接收器与 a 进行比较,这就是 of 的类别。hashed_pw
my_pw
String
==
String#==
String
BCrypt::Password
hashed_pw
如果你仔细想想,这是有道理的:BCrypt::Password
一个来自 Ruby 之外的第三方类,一个内置的 Ruby 类怎么会知道在实现该类时甚至不存在的东西String
?
另一方面,在第二个示例中,您将消息发送==
到,作为参数hashed_pw
传递。my_pw
此消息被分派给该BCrypt::Password#==
方法,该方法确实知道如何将接收者与 a 进行比较String
:
方法:BCrypt::Password#==
定义在:lib/bcrypt/password.rb
#==(secret)
⇒Object
也称为:is_password?
将潜在秘密与哈希进行比较。true
如果秘密是原始秘密,则返回,false
否则。
实际上,这种特殊情况下的问题比最初看起来更微妙。
我在上面写了String#==
不知道如何处理 aBCrypt::Password
作为参数,因为它只知道如何比较String
s。嗯,实际上BCrypt::Password
继承自String
,意思是一个BCrypt::Password
IS-A String
,所以String#==
应该知道如何处理它!
但想想是什么String#==
:
string == object
→true
或false
true
如果object
长度和内容相同,则返回;如self
; false
否则 […]
想一想:“true
如果object
长度和内容相同,则返回”。对于哈希,这实际上永远不会是真的。self
will be something like'P@$$w0rd!'
和object
will be something like '$2y$12$bxWYpd83lWyIr.dF62eO7.gp4ldf2hMxDofXPwdDZsnc2bCE7hN9q'
,很明显,它们的长度和内容都不相同。并且object
不可能是相同的内容,因为加密安全密码哈希的全部意义在于您无法反转它。因此,即使object
以某种方式想将自己显示为原始密码,它也无法做到。
唯一可行的方法是,如果String
并且BCrypt::Password
可以以某种方式“共同努力”来确定平等。
现在,如果我们仔细查看 的文档String#==
,实际上有一种方法可以完成这项工作:
如果object
不是 String 的实例但响应to_str
,则使用 比较两个字符串object.==
。
所以,如果作者BCrypt::Password
做出了不同的设计决定,那么它会起作用:
- 不要让
BCrypt::Password
继承自String
.
- 实施
BCrypt::Password#to_str
。这实际上将允许BCrypt::Password
与实际互换使用,String
因为任何接受String
s 的方法也应该接受响应 的对象to_str
。
现在,根据 的文档String#==
,如果您编写my_pw == hashed_pw
,会发生以下情况:
String#==
通知hashed_pw
不是String
.
String#==
hashed_pw
确实响应的通知to_str
。
- 因此,它将发送消息
object == self
(在我们的情况下相当于hashed_pw == my_pw
),这意味着我们现在处于您问题的第二种情况,这工作得很好。
这是一个如何工作的示例:
class Pwd
def initialize(s)
@s = s.downcase.freeze
end
def to_str
p __callee__
@s.dup.freeze
end
def ==(other)
p __callee__, other
@s == other.downcase
end
end
p = Pwd.new('HELLO')
s = 'hello'
p == s
# :==
# "hello"
#=> true
s == p
# :==
# "hello"
#=> true
如您所见,我们得到了预期的结果,并且Pwd#==
两次都被调用。此外,to_str
永远不会被调用,它只会被String#==
.
因此,具有讽刺意味的是,问题不在于String#==
不知道如何处理BCrypt::Password
对象,而实际上问题在于它确实知道如何将它们作为泛型String
s处理。如果他们不是String
s 而只是回应to_str
,那么String#==
实际上会知道向他们寻求帮助。
Numeric
Ruby 中的对象具有完整的强制协议,以确保即使第三方数字库也支持不同“类似数字”操作数类型之间的算术运算。