Как правильно реализовать 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
}
}
Но это приводит к ошибке:
Операторная функция переопределяет "конечную" операторную функцию
OK. После некоторых исследований (все еще изучая 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
и ==
правильно в базовом классе и подклассе?
Ответы
Ответ 1
После многих исследований и некоторых проб и ошибок я наконец придумал рабочее решение. Первым шагом было перемещение оператора ==
изнутри класса в глобальную область. Это фиксировало ошибки около 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
}
Ответ 2
Я знаю, что прошло некоторое время, так как вопрос отправлен, но я надеюсь, что мой ответ поможет.
TL;DR. Вместо того, чтобы пытаться переопределить ==
, вы предоставляете собственный метод сравнения, вызываете его ==
и при необходимости переопределяете метод пользовательского сравнения.
Итак, вы сказали
Все классы будут использоваться только в 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
Ответ 3
После других ответов я придумал это:
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)
}
}
Ответ 4
Следуя ответу 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
}
"""