Swift:在子类中覆盖 == 结果仅在超类中调用 ==
Swift: Overriding == in subclass results invocation of == in superclass only
我有一个classA
,它符合Equatable
协议,实现了==
功能。在 subclass B
中,我用更多检查覆盖了 ==
。
但是,当我比较 B
的两个实例数组(它们都具有 Array<A>
类型)时,会调用 ==
for A
。当然,如果我将两个数组的类型更改为 Array<B>
,将调用 B
的 ==
。
我想出了以下解决方案:
A.swift:
internal func ==(lhs: A, rhs: A) -> Bool {
if lhs is B && rhs is B {
return lhs as! B == rhs as! B
}
return ...
}
这看起来真的很难看,必须为 A
的每个子 class 扩展。有没有办法确保首先调用 subclass 的 ==
?
为包含 B
的 Array<A>
调用 A
的相等性的原因是自由函数的重载是静态解析的,而不是动态解析的——也就是说,在编译时基于类型的时间,而不是基于指向值的运行时。
这并不奇怪,因为 ==
没有在 class 中声明,然后在子 class 中被覆盖。这可能看起来非常有限,但老实说,使用传统的 OO 技术定义多态相等性非常(并且看似)困难。有关详细信息,请参阅 this link and 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. When x == y
, x
and y
are interchangeable in any code that only depends on their values.
Class instance identity as distinguished by triple-equals ===
is
notably not part of an instance's value. Exposing other non-value
aspects of Equatable
types is discouraged, and any that are
exposed should be explicitly pointed out in documentation.
鉴于此,如果您实现平等的方式 而不是 ,您可能会认真考虑重新考虑 Equatable
实现很高兴两个值相等,可以相互替换。避免这种情况的一种方法是将对象标识视为相等性的度量,并根据 ===
实现 ==
,对于 superclass 只需执行一次。或者,您可以问问自己,您真的 需要实现继承吗?如果不是,请考虑放弃它并改用值类型,然后使用协议和泛型来捕获您正在寻找的多态行为。
我 运行 遇到了类似的问题,因为我想在 MKPointAnnotation 子类(继承自 NSObject)上使用 difference(from:)
。即使我将 func ==
添加到我的注释子类中,difference(from:
仍然会调用 NSObject 的 ==
实现。我相信这只是比较了 2 个对象的内存位置,这不是我想要的。为了使 difference(from:
正常工作,我必须为我的注释子类实现 override func isEqual(_ object: Any?) -> Bool {
。在正文中,我会确保 object
与我的子类是同一类型,然后在那里进行比较。
我有一个classA
,它符合Equatable
协议,实现了==
功能。在 subclass B
中,我用更多检查覆盖了 ==
。
但是,当我比较 B
的两个实例数组(它们都具有 Array<A>
类型)时,会调用 ==
for A
。当然,如果我将两个数组的类型更改为 Array<B>
,将调用 B
的 ==
。
我想出了以下解决方案:
A.swift:
internal func ==(lhs: A, rhs: A) -> Bool {
if lhs is B && rhs is B {
return lhs as! B == rhs as! B
}
return ...
}
这看起来真的很难看,必须为 A
的每个子 class 扩展。有没有办法确保首先调用 subclass 的 ==
?
为包含 B
的 Array<A>
调用 A
的相等性的原因是自由函数的重载是静态解析的,而不是动态解析的——也就是说,在编译时基于类型的时间,而不是基于指向值的运行时。
这并不奇怪,因为 ==
没有在 class 中声明,然后在子 class 中被覆盖。这可能看起来非常有限,但老实说,使用传统的 OO 技术定义多态相等性非常(并且看似)困难。有关详细信息,请参阅 this link and 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. When
x == y
,x
andy
are interchangeable in any code that only depends on their values.Class instance identity as distinguished by triple-equals
===
is notably not part of an instance's value. Exposing other non-value aspects ofEquatable
types is discouraged, and any that are exposed should be explicitly pointed out in documentation.
鉴于此,如果您实现平等的方式 而不是 ,您可能会认真考虑重新考虑 Equatable
实现很高兴两个值相等,可以相互替换。避免这种情况的一种方法是将对象标识视为相等性的度量,并根据 ===
实现 ==
,对于 superclass 只需执行一次。或者,您可以问问自己,您真的 需要实现继承吗?如果不是,请考虑放弃它并改用值类型,然后使用协议和泛型来捕获您正在寻找的多态行为。
我 运行 遇到了类似的问题,因为我想在 MKPointAnnotation 子类(继承自 NSObject)上使用 difference(from:)
。即使我将 func ==
添加到我的注释子类中,difference(from:
仍然会调用 NSObject 的 ==
实现。我相信这只是比较了 2 个对象的内存位置,这不是我想要的。为了使 difference(from:
正常工作,我必须为我的注释子类实现 override func isEqual(_ object: Any?) -> Bool {
。在正文中,我会确保 object
与我的子类是同一类型,然后在那里进行比较。