但是,当我在B的两个实例数组(两者都具有类型Array< A>)之间进行比较时,调用A = A。当然,如果我将两个数组的类型更改为Array< B>,则调用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的每个子类进行扩展。有没有办法确保首先调用子类的==?
这并不奇怪,因为==未在类中声明,然后在子类中重写。这似乎是非常有限的,但老实说,使用传统的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 } }
你还需要做一个吗?跳舞,因为你需要确定右手参数是否是B(如果等于直接取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实现,如果你实现相等的方式不是你对两个相等的值相互替换感到满意的方式。避免这种情况的一种方法是将对象标识视为相等的度量,并根据===实现==,这对于超类只需要执行一次。或者,你可以问自己,你真的需要实现继承吗?如果没有,请考虑抛弃它并使用值类型,然后使用协议和泛型来捕获您正在寻找的多态行为。