提问人:schande 提问时间:2/15/2021 更新时间:2/15/2021 访问量:92
在 Ruby 中,相等运算符的顺序重要吗?
Is the order of the equality operator important in Ruby?
问:
我已经在我的 Ruby 程序中使用了 bcrypt 库。我注意到相等运算符的顺序似乎很重要。根据“==”的左边或右边的变量,我会得到不同的结果。 下面是一个示例程序:
require 'bcrypt'
my_pw = "pw1"
puts "This is my unhashed password: #{my_pw}"
hashed_pw = BCrypt::Password.create(my_pw)
puts "This is my hashed password: #{hashed_pw}"
20.times{print"-"}
puts
puts "my_pw == hashed_pw equals:"
if (my_pw == hashed_pw)
puts "TRUE"
else
puts "FALSE"
end
puts "hashed_pw == my_pw equals:"
if (hashed_pw == my_pw)
puts "TRUE"
else
puts "FALSE"
end
问候 尚德
答:
例如,如果两个操作数都是 String 类型,则表达式是等效的。在您的例子中,一个操作数是 a,另一个是 .因此,调用 String 类中定义的相等方法,同时调用 BCrypt::P assword 中定义的相等方法。String
BCrypt::Password
my_pw == hashed_pw
hashed_pw == my_pw
我从未使用过 BCrypt::P assword,但希望您能得到前者和后者。false
true
评论
hashed_pw
String
BCrypt::Password
String
x.class == Y
x.is_a?(Y)
String
Password
Password
p = BCrypt::Password.create("my pretty password")
p.include?("$12$")
true
p.include?("pretty")
false
::create
#==
在 Ruby 中,您可以覆盖给定类或实例的相等方法:
class Test
define_method(:==) do |_other|
true
end
end
Test.new == 'foo' # => true
Test.new == nil # => true
Test.new == 42 # => true
'foo' == Test.new # => false
nil == Test.new # => false
42 == Test.new # => true
一般来说,在不使其对称的情况下凌驾于平等之上被认为是不好的做法,但有时您会在野外看到它。
评论
def ==(other)
是的,有区别。
my_pw == hashed_pw
调用字符串上的方法并作为参数传递。这意味着您正在使用该方法。来自 String#==
的文档:==
my_pw
hashed_pw
String#==
string == object → true or false
如果具有相同的长度和内容,则返回;如; 否则
true
object
self
false
while 在 的实例上调用方法并作为参数传递。来自 BCrypt::P assword#==
的文档:hashed_pw == my_pw
==
BCrypt::Password
my_pw
#==(secret) ⇒ Object
将潜在机密与哈希值进行比较。如果密钥是原始密钥,则返回,否则返回。
true
false
这与平等没有任何关系。这只是面向对象编程的基础知识。
在 OOP 中,所有计算都是由对象向其他对象发送消息来完成的。OOP 的一个基本属性是接收方对象,并且只有接收方对象决定如何响应此消息。这就是在 OOP 中实现封装、数据隐藏和抽象的方式。
因此,如果您将消息发送到作为参数传递的对象,则可以决定如何解释此消息。如果将消息发送到作为参数传递的对象,则由它来决定如何解释此消息。没有内置机制可以保证这一点,并以相同的方式解释此消息。只有当两个对象决定相互协调时,响应才会真正相同。m
a
b
a
m
b
a
b
a
b
如果你仔细想想,如果结果相同,那就太奇怪了。2 - 3
3 - 2
这正是这里发生的事情:在第一个示例中,您将消息发送到 ,作为参数传递。 是 String 类的实例,因此消息将被调度到
String#==
方法。此方法知道如何将接收对象与另一个对象进行比较。但是,它不知道如何将接收器与 BCrypt::P assword
进行比较,这就是 的类。==
my_pw
hashed_pw
my_pw
==
String
hashed_pw
如果你仔细想想,这是有道理的:如果是 Ruby 之外的第三方类,一个内置的 Ruby 类怎么可能知道在实现该类时甚至不存在的东西?BCrypt::Password
String
另一方面,在第二个示例中,您将消息发送到 ,作为参数传递。此消息被调度到 BCrypt::P assword#==
方法,该方法知道如何将接收方与 :==
hashed_pw
my_pw
String
方法:
BCrypt::Password#==
定义于:lib/bcrypt/password.rb
#==(secret)
⇒
也称为:Object
is_password?
将潜在机密与哈希值进行比较。如果密钥是原始密钥,则返回,否则返回。
true
false
实际上,在这种特殊情况下,问题比最初看起来要微妙得多。
我在上面写过,不知道如何处理 a 作为参数,因为它只知道如何比较 s。嗯,其实继承自,意思是IS-A,所以应该知道如何处理它!String#==
BCrypt::Password
String
BCrypt::Password
String
BCrypt::Password
String
String#==
但是想想有什么作用:String#==
string == object
→ 或true
false
如果具有相同的长度和内容,则返回;如; 否则 [...]
true
object
self
false
想想看:“如果长度和内容相同,则返回”。对于哈希来说,这实际上永远不会是真的。 will be something like 和 will be something like,所以很明显,它们既不是相同的长度,也不是相同的内容。并且不可能是相同的内容,因为加密安全密码哈希的全部意义在于您无法反转它。因此,即使以某种方式想将自己呈现为原始密码,它也无法做到。true
object
self
'P@$$w0rd!'
object
'$2y$12$bxWYpd83lWyIr.dF62eO7.gp4ldf2hMxDofXPwdDZsnc2bCE7hN9q'
object
object
唯一可行的方法是,如果并且能够以某种方式“共同努力”来弄清楚平等。String
BCrypt::Password
现在,如果我们仔细查看 的文档,实际上有一种方法可以使它工作:String#==
如果不是 String 的实例,但响应 ,则使用 比较这两个字符串。
object
to_str
object.==
因此,如果作者做出了不同的设计决定,那么它将起作用:BCrypt::Password
- 不要让继承自 。
BCrypt::Password
String
- 实现。这实际上允许与 互换使用,因为任何接受 s 的方法也应该接受响应 的对象。
BCrypt::Password#to_str
BCrypt::Password
String
String
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
to_str
String#==
评论
==
就像大多数 Ruby 运算符解析为方法调用一样,因此行为取决于其接收者。