Как правильно реализовать 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
}
`` `