【问题标题】:How to properly implement the Equatable protocol in a class hierarchy?如何在类层次结构中正确实现 Equatable 协议?
【发布时间】:2017-02-16 00:20:13
【问题描述】:

我正在尝试在 Swift 3 中的基类及其子类中实现 == 运算符(来自 Equatable)。所有类都只会在 Swift 中使用,所以我不想涉及 @987654323 @ 或 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 以指示可以覆盖类型方法。

所以我尝试将Base 中的static 更改为class

class Base : Equatable {
    var x : Int

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

但这会导致一个新的错误:

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

呃。这比应有的复杂得多。

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

【问题讨论】:

    标签: swift subclass swift3 equatable


    【解决方案1】:

    根据我想出的其他答案:

    class Base : Equatable {
        var x : Int
        static func == (lhs: Base, rhs: Base) -> Bool {
            return lhs.x == rhs.x
        }
    }
    
    class Subclass : Base {
        var y : String
        static func == (lhs: Subclass, rhs: Subclass) -> Bool {
            return lhs.y == rhs.y && (lhs as Base) == (rhs as Base)
        }
    }
    

    【讨论】:

      【解决方案2】:

      rmaddy's answer 之后,我想出了一种用于测试相等性的保护方法:

      Base.swift


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

      子类.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
      }
      

      ```

      【讨论】:

        【解决方案3】:

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

        对于基类,这变成了:

        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
        }
        

        子类.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
        }
        

        【讨论】:

        • 哇。巧妙的解决方法,但这真的是 Swift 让我们做的事情吗?
        【解决方案4】:

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

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


        你说的

        所有的类都只会在 Swift 中使用,所以我不想涉及 NSObjectNSCopying 协议。

        但是如果您继承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.您在子类中的isEqual 实现应该在仅检查子类的属性后调用super.isEqual。子类不应对其父类的任何属性进行任何检查。 2.与问题无关,但您的爷爷,父亲,儿子的阶级层次是倒退的。从逻辑上讲,儿子不是父亲,父亲不是爷爷。 Son 类应该是根类。父亲应该扩展儿子,爷爷应该扩展父亲。
        猜你喜欢
        • 2015-05-14
        • 1970-01-01
        • 2015-11-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-01-30
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多