问题 如何在类层次结构中正确实现Equatable协议?


我正在努力实施 == 操作员(来自 Equatable)在Swift 3中的基类及其子类中。所有类只在Swift中使用,所以我不想涉及 NSObject 或者 NSCopying 协议。

我从一个基类和一个子类开始:

class Base {
    var x : Int
}

class Subclass : Base {
    var y : String
}

现在我想补充一下 Equatable 和 == 运营商 Base。看起来很简单。复制 == 文档中的操作员签名:

class Base : Equatable {
    var x : Int

    static func == (lhs: Base, rhs: Base) -> Bool {
        return lhs.x == rhs.x
    }
}

到现在为止还挺好。现在为子类:

class Subclass : Base {
    static override func == (lhs: Base, rhs: Base) -> Bool {
        return true
    }
}

但这会导致错误:

运算符函数会覆盖“最终”运算符函数

好。经过一些研究(仍在学习Swift 3),我学到了这一点 static 可以替换为 class 表示可以覆盖类型方法。

所以我试图改变 static 至 class 在 Base

class Base : Equatable {
    var x : Int

    class func == (lhs: Base, rhs: Base) -> Bool {
        return lhs.x == rhs.x
    }
}

但这会导致新的错误:

在非最终类'Base'中声明的运算符'=='必须是'final'

啊。这比它应该复杂得多。

我该如何实现 Equatable 协议和 == 运算符在基类和子类中是否正确?


6242
2017-10-07 05:08


起源



答案:


经过大量的研究和一些反复试验,我终于想出了一个有效的解决方案。第一步是移动 == 从类内部到全局范围的运算符。这解决了错误 static 和 final

对于基类,这成了:

func == (lhs: Base, rhs: Base) -> Bool {
    return lhs.x == rhs.x
}

class Base : Equatable {
    var x : Int
}

对于子类:

func == (lhs: Subclass, rhs: Subclass) -> Bool {
    return true
}

class Subclass : Base {
    var y : String
}

现在剩下的唯一部分是弄清楚如何打电话给 == 来自的基类的运算符 == 子类的运算符。这导致了我的最终解决方案:

func == (lhs: Subclass, rhs: Subclass) -> Bool {
    if lhs.y == rhs.y {
        if lhs as Base == rhs as Base {
            return true
        }
    }

    return false
}

第一个 if 声明导致调用 == 基类中的运算符。


最终的解决方案:

Base.swift:

func == (lhs: Base, rhs: Base) -> Bool {
    return lhs.x == rhs.x
}

class Base : Equatable {
    var x : Int
}

Subclass.swift:

func == (lhs: Subclass, rhs: Subclass) -> Bool {
    if lhs.y == rhs.y {
        if lhs as Base == rhs as Base {
            return true
        }
    }

    return false
}

class Subclass : Base {
    var y : String
}

11
2017-10-07 05:08



哇。聪明的解决方案,但这真的是斯威夫特让我们做的事情吗? - jeff-h


答案:


经过大量的研究和一些反复试验,我终于想出了一个有效的解决方案。第一步是移动 == 从类内部到全局范围的运算符。这解决了错误 static 和 final

对于基类,这成了:

func == (lhs: Base, rhs: Base) -> Bool {
    return lhs.x == rhs.x
}

class Base : Equatable {
    var x : Int
}

对于子类:

func == (lhs: Subclass, rhs: Subclass) -> Bool {
    return true
}

class Subclass : Base {
    var y : String
}

现在剩下的唯一部分是弄清楚如何打电话给 == 来自的基类的运算符 == 子类的运算符。这导致了我的最终解决方案:

func == (lhs: Subclass, rhs: Subclass) -> Bool {
    if lhs.y == rhs.y {
        if lhs as Base == rhs as Base {
            return true
        }
    }

    return false
}

第一个 if 声明导致调用 == 基类中的运算符。


最终的解决方案:

Base.swift:

func == (lhs: Base, rhs: Base) -> Bool {
    return lhs.x == rhs.x
}

class Base : Equatable {
    var x : Int
}

Subclass.swift:

func == (lhs: Subclass, rhs: Subclass) -> Bool {
    if lhs.y == rhs.y {
        if lhs as Base == rhs as Base {
            return true
        }
    }

    return false
}

class Subclass : Base {
    var y : String
}

11
2017-10-07 05:08



哇。聪明的解决方案,但这真的是斯威夫特让我们做的事情吗? - jeff-h


我知道问题发布已经有一段时间了,但我希望我的答案有所帮助。

TLDR - 而不是试图覆盖 ==,您提供自定义比较方法,make == 调用它,并在需要时覆盖自定义比较方法。


所以你说

所有类只会在Swift中使用,所以我不想涉及 NSObject 或者 NSCopying 协议。

但如果你  到子类 NSObject,您将如何编写自定义比较方法?你会覆盖 isEqual(Any?), 对?如果你试图遵守 Equatable 在您的子类中的协议,编译器将抱怨“冗余符合协议 Equatable“因为 NSObject 已经符合 Equatable

现在,这给了我们一些关于如何的提示 NSObject 处理这个问题 - 它提供了一个自定义的比较方法 isEqual(Any?),在里面打电话 ==,如果需要,它的子类可以覆盖它。您可以在自己的基类中执行相同的操作。

不用多说,让我们做一些实验(在Swift 4中)。定义一些类

class Grandpa: Equatable {
    var x = 0

    static func ==(lhs: Grandpa, rhs: Grandpa) -> Bool {
        return lhs.isEqual(to: rhs)
    }

    func isEqual(to object: Any?) -> Bool {
        guard object != nil && type(of: object!) == Grandpa.self else {
            return false
        }
        let value = object as! Grandpa
        return x == value.x
    }
}

class Father: Grandpa {
    var y = 0

    override func isEqual(to object: Any?) -> Bool {
        guard object != nil && type(of: object!) == Father.self else {
            return false
        }
        let value = object as! Father
        return x == value.x && y == value.y
    }
}

class Son: Father {
    var z = 0

    override func isEqual(to object: Any?) -> Bool {
        guard object != nil && type(of: object!) == Son.self else {
            return false
        }
        let value = object as! Son
        return x == value.x && y == value.y && z == value.z
    }
}

并编写一些测试代码

let grandpa1 = Grandpa()
let grandpa2 = Grandpa()
let grandpa3: Grandpa? = nil
let grandpa4: Grandpa? = nil
let father1 = Father()
let father2 = Father()
let father3 = Father()
father3.y = 1
let son1 = Son()
let son2 = Son()
let son3 = Son()
son3.z = 1

print("grandpa1 == grandpa2: \(grandpa1 == grandpa2)")
print("grandpa1 == grandpa3: \(grandpa1 == grandpa3)")
print("grandpa3 == grandpa4: \(grandpa3 == grandpa4)")
print("grandpa1 == father1: \(grandpa1 == father1)")
print("father1 == father2: \(father1 == father2)")
print("father1 == father3: \(father1 == father3)")
print("son1 == son2: \(son1 == son2)")
print("son1 == son3: \(son1 == son3)")

跑吧,你应该得到

grandpa1 == grandpa2: true
grandpa1 == grandpa3: false
grandpa3 == grandpa4: true
grandpa1 == father1: false
father1 == father2: true
father1 == father3: false
son1 == son2: true
son1 == son3: false

1
2018-01-09 10:03



1.你的实施 isEqual 在子类中应该调用 super.isEqual 在检查了子类的属性之后。子类不应该检查其父类的任何属性。 2.与这个问题没有关系,但你的爷爷,父亲,儿子阶级等级是倒退的。从逻辑上讲,儿子不是父亲,父亲不是父亲。 Son类应该是root类。父亲应该伸出儿子,而爷爷应该伸出父亲。 - rmaddy


以下 rmaddy的回答,我想出了一个测试平等的守卫方法:

Base.swift


static func ==(lhs: Base, rhs: Base) -> Bool {
    // ensure class properties match
    guard lhs.x == rhs.x else {
        return false
    }

    return true

}

Subclass.swift


static func ==(lhs: Subclass, rhs: Subclass) -> Bool {
    // ensure base class properties match
    guard lhs as Base == rhs as Base else {
        return false
    }

    // ensure class properties match
    guard lhs.y == rhs.y else {
        return false
    }

    return true
}

```


-1
2018-05-24 01:32