对于包含B
的Array<A>
调用等于A
的原因是,自由函数的重载是静态解决的,而不是动态解决的——也就是说,在编译时基于类型,而不是在运行时基于指向的值.
这并不奇怪,因为==
没有在类内声明,然后在子类中重写.这似乎非常有限,但老实说,使用传统的OO技术定义多态性平等是极其困难的(而且是虚假的).更多信息请参见this link和this paper.
天真的解决方案可能是在A
中定义一个动态调度函数,然后定义==
来调用:
class A: Equatable {
func equalTo(rhs: A) -> Bool {
// whatever equality means for two As
}
}
func ==(lhs: A, rhs: A) -> Bool {
return lhs.equalTo(rhs)
}
然后,当你实现B
时,你会覆盖equalTo
:
class B: A {
override func equalTo(rhs: A) -> Bool {
return (rhs as? B).map { b in
return // whatever it means for two Bs to be equal
} ?? false // false, assuming a B and an A can’t be Equal
}
}
你仍然需要跳as?
支舞,因为你需要确定右边的参数是否是B
(如果equalTo
直接取B
,那就不是合法的覆盖).
这里还隐藏着一些可能令人惊讶的行为:
let x: [A] = [B()]
let y: [A] = [A()]
// this runs B’s equalTo
x == y
// this runs A’s equalTo
y == x
也就是说,参数的顺序会改变行为.这并不好——人们期望平等是对称的.所以你真的需要上面链接中描述的一些技术来正确解决这个问题.
在这一点上,你可能会觉得这一切都变得有点不必要了.很可能是这样,特别是考虑到Swift标准库中Equatable
的文档中的以下注释:
Equality implies substitutability.当x == y
、x
和y
三重等于===
的类实例标识是
考虑到这一点,如果你实现平等的方式是not,你可能会很高兴两个相等的值被彼此替换,那么你可能会认真地重新考虑对你的Equatable
实现产生兴趣.避免这种情况的一种方法是将对象标识看作是平等的度量,并用===
来实现==
,这只需要对超类进行一次.或者,您可以问自己,是否需要实现继承?如果不是,考虑放弃它并使用值类型,然后使用协议和泛型来捕获您正在寻找的多态行为.