Swift:在子类中重写==仅在超类中导致==的调用

问题描述 投票:27回答:2

我有一个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的每个子类进行扩展。有没有办法确保首先调用子类的==

swift overriding equality
2个回答
41
投票

之所以要为包含AArray<A>调用B的相等性,是因为静态地解决了自由函数的重载,而不是动态地解决了–即在基于类型的编译时,而不是在运行时基于指向的值。

这不足为奇,因为==没有在类内部声明,然后在子类中被覆盖。这似乎很局限,但说实话,使用传统的OO技术定义多态相等性是极其困难的。有关更多信息,请参见this linkthis 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 == yxy时可以在任何仅取决于其值的代码中互换。

以三等式===区分的类实例标识为显然不是实例值的一部分。暴露其他非价值不鼓励使用Equatable类型的方面,以及任何are应该在文档中明确指出暴露的对象。

鉴于此,如果您实现相等性的方式是not,并且您很乐意用两个相等的值替换,那么您可能会很想重新考虑使用Equatable实现彼此。避免这种情况的一种方法是将对象标识视为相等性的度量,并根据==来实现===,对于超类只需要执行一次。或者,您可以问问自己,您[[really是否需要实现继承?如果不是这样,请考虑放弃它,而是使用值类型,然后使用协议和泛型捕获您要查找的多态行为。


0
投票
我遇到了类似的问题,因为我想在MKPointAnnotation子类(继承自NSObject)上使用difference(from:)。即使将func ==添加到注释子类中,difference(from:仍将调用NSObject的==实现。我相信这只是比较2个对象的内存位置,这不是我想要的。为了使difference(from:正常工作,我必须为我的注释子类实现override func isEqual(_ object: Any?) -> Bool {。在主体中,我将确保object与子类的类型相同,然后在其中进行比较。
© www.soinside.com 2019 - 2024. All rights reserved.