Как правильно реализовать 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 протокол и == оператор правильно в базовом классе и подклассе?

3 ответа

Решение

После долгих исследований и проб и ошибок я наконец нашел рабочее решение. Первым шагом было перемещение == оператор изнутри класса в глобальную область видимости. Это исправило ошибки о 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
}

Следуя другим ответам, я придумал это:

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)
    }
}

Я знаю, что прошло много времени с тех пор, как вопрос опубликован, но я надеюсь, что мой ответ поможет.

TLDR - вместо попытки переопределить ==, вы предоставляете пользовательский метод сравнения, сделать == вызовите его и при необходимости измените пользовательский метод сравнения.


Так ты сказал

Все классы будут использоваться только в 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

После ответа Рмадди я предложил осторожный подход к проверке равенства:

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
}

`` `

Другие вопросы по тегам