2016-10-07 3 views
10

Я пытаюсь реализовать оператор == (от Equatable) в базовом классе и его подклассах в Swift 3. Все классы будут использоваться только в Swift, поэтому я не хочу включать NSObject или протокол NSCopying.Как правильно реализовать Equatable протокол в иерархии классов?

Я начал с базового класса и подкласса:

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

Но это приводит к ошибке:

Operator function overrides a 'final' operator function

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

Но это приводит к новой ошибке:

Operator '==' declared in non-final class 'Base' must be 'final'

тьфу. Это гораздо сложнее, чем должно быть.

Как реализовать протокол Equatable и оператор == в базовом классе и подклассе?

ответ

8

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


Конечный раствор:

база.скор:

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 
} 
+1

Wow. Умный обход, но разве это ДЕЙСТВИТЕЛЬНО то, что Стрит делает нас? –

0

Я знаю, что это было некоторое время, так как вопрос отвечал, но я надеюсь, что мой ответ помогает.

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


Так вы сказали

All of the classes will only be used in Swift so I do not want to involve NSObject or the NSCopying protocol.

Но если вы были подкласс 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 
+0

1. Ваша реализация 'isEqual' в подклассах должна вызывать' super.isEqual' после проверки свойств только подкласса. Подкласс не должен проверять какие-либо свойства его родительских классов. 2. Не имеет отношения к вопросу, но ваша иерархия классов GrandPa, Father, Son обратная. По логике, Сын не Отец, а Отец - не Великая Папа. Класс Сон должен быть классом корня. Отец должен продлить Сына, а Великий Отец должен продлить Отца. – rmaddy