我有一个A
类,它符合Equatable
协议并实现了==
功能。在子类B
中,我通过更多检查覆盖了==
。
但是,当我在B
的两个实例数组(都具有Array<A>
类型)之间进行比较时,将调用==
的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
的每个子类进行扩展。有没有办法确保首先调用子类的==
?
之所以要为包含A
的Array<A>
调用B
的相等性,是因为静态地解决了自由函数的重载,而不是动态地解决了–即在基于类型的编译时,而不是在运行时基于指向的值。
这不足为奇,因为==
没有在类内部声明,然后在子类中被覆盖。这似乎很局限,但说实话,使用传统的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
文档中给出了以下注释:
平等意味着可替代性。当
x == y
,x
和y
时可以在任何仅取决于其值的代码中互换。以三等式
===
区分的类实例标识为显然不是实例值的一部分。暴露其他非价值不鼓励使用Equatable
类型的方面,以及任何are应该在文档中明确指出暴露的对象。
鉴于此,如果您实现相等性的方式是not,并且您很乐意用两个相等的值替换,那么您可能会很想重新考虑使用Equatable
实现彼此。避免这种情况的一种方法是将对象标识视为相等性的度量,并根据==
来实现===
,对于超类只需要执行一次。或者,您可以问问自己,您[[really是否需要实现继承?如果不是这样,请考虑放弃它,而是使用值类型,然后使用协议和泛型捕获您要查找的多态行为。
difference(from:)
。即使将func ==
添加到注释子类中,difference(from:
仍将调用NSObject的==
实现。我相信这只是比较2个对象的内存位置,这不是我想要的。为了使difference(from:
正常工作,我必须为我的注释子类实现override func isEqual(_ object: Any?) -> Bool {
。在主体中,我将确保object
与子类的类型相同,然后在其中进行比较。