web-dev-qa-db-fra.com

Comment implémenter correctement le protocole Equatable dans une hiérarchie de classes?

J'essaie d'implémenter l'opérateur == (à partir de Equatable) dans une classe de base et ses sous-classes dans Swift 3. Toutes les classes ne seront utilisées que dans Swift, je ne souhaite donc pas impliquer NSObject ou le protocole NSCopying.

J'ai commencé avec une classe de base et une sous-classe:

class Base {
    var x : Int
}

class Subclass : Base {
    var y : String
}

Maintenant, je voulais ajouter Equatable et l'opérateur == à Base. Cela semble assez simple. Copiez la signature de l'opérateur == à partir de la documentation:

class Base : Equatable {
    var x : Int

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

Jusqu'ici tout va bien. Passons maintenant à la sous-classe:

class Subclass : Base {
    static override func == (lhs: Base, rhs: Base) -> Bool {
        return true
    }
}

Mais cela entraîne une erreur:

La fonction opérateur remplace une fonction opérateur 'finale'

D'ACCORD. Après quelques recherches (j'apprends toujours Swift 3), j'apprends que static peut être remplacé par class pour indiquer que la méthode type peut être remplacée.

Je tente donc de remplacer static par class dans Base:

class Base : Equatable {
    var x : Int

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

Mais cela entraîne une nouvelle erreur:

L'opérateur '==' déclaré dans la classe non finale 'Base' doit être 'final'

Pouah. C'est beaucoup plus compliqué que cela ne devrait être.

Comment implémenter correctement le protocole Equatable et l'opérateur == dans une classe de base et une sous-classe?

13
rmaddy

Après de nombreuses recherches et quelques essais et erreurs, j'ai finalement trouvé une solution efficace. La première étape a été de déplacer l'opérateur == de l'intérieur de la classe vers la portée globale. Cela corrigeait les erreurs concernant static et final.

Pour la classe de base, ceci est devenu:

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

class Base : Equatable {
    var x : Int
}

Et pour la sous-classe:

func == (lhs: Subclass, rhs: Subclass) -> Bool {
    return true
}

class Subclass : Base {
    var y : String
}

Maintenant, il ne reste plus qu'à comprendre comment appeler l'opérateur == de la classe de base à partir de l'opérateur == de la sous-classe. Cela m'a conduit à la solution finale:

func == (lhs: Subclass, rhs: Subclass) -> Bool {
    if lhs.y == rhs.y {
        if lhs as Base == rhs as Base {
            return true
        }
    }

    return false
}

Cette première instruction if entraîne l'appel de l'opérateur == dans la classe de base.


La solution finale:

Base.Swift:

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

class Base : Equatable {
    var x : Int
}

Sous-classe.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
}
12
rmaddy

Je sais que la question a été postée il y a longtemps, mais j'espère que ma réponse aidera.

TLDR - Au lieu d'essayer de remplacer ==, vous fournissez une méthode de comparaison personnalisée, faites appeler == et remplacez la méthode de comparaison personnalisée si nécessaire.


Alors tu as dit

Toutes les classes ne seront utilisées que dans Swift, je ne souhaite donc pas utiliser NSObject ni le protocole NSCopying.

Mais si vous étiez dans la sous-classe NSObject, comment allez-vous écrire votre méthode de comparaison personnalisée? Vous annulerez isEqual(Any?), non? Et si vous essayez de vous conformer au protocole Equatable dans votre sous-classe, le compilateur se plaindra de la "conformité redondante au protocole Equatable" car NSObject est déjà conforme à Equatable.

Maintenant, cela nous donne quelques indications sur la façon dont NSObject traite ce problème - il fournit une méthode de comparaison personnalisée isEqual(Any?), appelez-le dans == et ses sous-classes peuvent le remplacer si nécessaire. Vous pouvez faire la même chose dans votre propre classe de base.

Sans plus tarder, faisons quelques expériences (dans Swift 4). Définir des classes

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

Et écris du code de test

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

Exécutez-le et vous devriez obtenir

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
Zheming Z

Après les autres réponses, j'ai trouvé ceci:

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)
    }
}
0
Vello Vaherpuu