Remplacement d'une propriété stockée dans Swift

126

J'ai remarqué que le compilateur ne me laissera pas remplacer une propriété stockée par une autre valeur stockée (ce qui semble étrange):

class Jedi {
    var lightSaberColor = "Blue"
}


class Sith: Jedi {
    override var lightSaberColor = "Red" // Cannot override with a stored property lightSaberColor
}

Cependant, je suis autorisé à le faire avec une propriété calculée:

class Jedi {
    let lightSaberColor = "Blue"
}


class Sith: Jedi {
    override var lightSaberColor : String{return "Red"}

}

Pourquoi ne suis-je pas autorisé à lui donner une autre valeur?

Pourquoi le remplacement d'une propriété stockée est-il une abomination et le faire avec une propriété calculée casher? Que pensent-ils?

cfischer
la source
1
duplication possible de la propriété de superclasse
Max MacLeod

Réponses:

82

Pourquoi ne suis-je pas autorisé à lui donner une autre valeur?

Vous êtes définitivement autorisé à attribuer une valeur différente à une propriété héritée. Vous pouvez le faire si vous initialisez la propriété dans un constructeur qui prend cette valeur initiale et transmettez une valeur différente de la classe dérivée:

class Jedi {
    // I made lightSaberColor read-only; you can make it writable if you prefer.
    let lightSaberColor : String
    init(_ lsc : String = "Blue") {
        lightSaberColor = lsc;
    }
}

class Sith : Jedi {
    init() {
        super.init("Red")
    }
}

let j1 = Jedi()
let j2 = Sith()

println(j1.lightSaberColor)
println(j2.lightSaberColor)

Remplacer une propriété n'est pas la même chose que lui donner une nouvelle valeur - c'est plutôt donner à une classe une propriété différente. En fait, c'est ce qui se produit lorsque vous substituez une propriété calculée: le code qui calcule la propriété dans la classe de base est remplacé par du code qui calcule le remplacement pour cette propriété dans la classe dérivée.

[Est-il] possible de remplacer la propriété stockée réelle, c'est lightSaberColor-à- dire qui a un autre comportement?

En dehors des observateurs, les propriétés stockées n'ont pas de comportement, il n'y a donc vraiment rien à remplacer. Donner à la propriété une valeur différente est possible grâce au mécanisme décrit ci-dessus. Cela fait exactement ce que l'exemple de la question essaie de réaliser, avec une syntaxe différente.

dasblinkenlight
la source
2
@MaxMacLeod En dehors des observateurs, les propriétés stockées n'ont pas de comportement, il n'y a donc vraiment rien à remplacer. Il voulait donner à une propriété stockée une valeur différente dans la sous-classe, mais il n'était pas sûr du mécanisme pour y parvenir. La réponse explique comment cela peut être fait dans Swift. Je suis désolé pour la réponse tardive, il semble que votre commentaire cause suffisamment de confusion pour attirer des votes négatifs, alors j'ai décidé d'expliquer ce qui se passe.
dasblinkenlight
55

Pour moi, votre exemple ne fonctionne pas dans Swift 3.0.1.

J'ai entré dans l'aire de jeux ce code:

class Jedi {
    let lightsaberColor = "Blue"
}

class Sith: Jedi {
    override var lightsaberColor : String {
        return "Red"
    }
}

Lève une erreur au moment de la compilation dans Xcode:

impossible de remplacer la propriété immuable 'let' 'lightsaberColor' avec le getter d'un 'var'

Non, vous ne pouvez pas modifier le type de propriété stockée. Le principe de substitution de Liskov vous oblige à autoriser qu'une sous-classe soit utilisée à un endroit où la superclasse est souhaitée.

Mais si vous le remplacez par varet par conséquent ajoutez le setdans la propriété calculée, vous pouvez remplacer la propriété stockée par une propriété calculée du même type.

class Jedi {
    var lightsaberColor = "Blue"
}


class Sith: Jedi {
    override var lightsaberColor : String {
        get {
            return "Red"
        }
        set {
            // nothing, because only red is allowed
        }
    }
}

Cela est possible car il peut être judicieux de passer d'une propriété stockée à une propriété calculée.

Mais remplacer une varpropriété stockée par une varpropriété stockée n'a pas de sens, car vous ne pouvez modifier que la valeur de la propriété.

Cependant, vous ne pouvez pas du tout remplacer une propriété stockée par une propriété stockée.


Je ne dirais pas que les Sith sont des Jedi :-P. Par conséquent, il est clair que cela ne peut pas fonctionner.

Binaire
la source
18
class SomeClass {
    var hello = "hello"
}
class ChildClass: SomeClass {
    override var hello: String {
        set {
            super.hello = newValue
        }
        get {
            return super.hello
        }    
    }
}
Zhiping Yang
la source
13
Bien que cet extrait de code puisse résoudre la question, inclure une explication contribue vraiment à améliorer la qualité de votre message. N'oubliez pas que vous répondez à la question aux lecteurs à l'avenir, et que ces personnes pourraient ne pas connaître les raisons de votre suggestion de code.
DimaSan
15

Vous souhaitez probablement attribuer une autre valeur à la propriété:

class Jedi {
    var lightSaberColor = "Blue"
}


class Sith: Jedi {
    override init() {
        super.init()
        self.lightSaberColor = "Red"
    }
}
zisoft
la source
9
il en va de même comme pour le commentaire ci
Max MacLeod
10

Pour Swift 4, à partir de la documentation Apple :

Vous pouvez remplacer une propriété d'instance ou de type héritée pour fournir vos propres getter et setter personnalisés pour cette propriété, ou pour ajouter des observateurs de propriété pour permettre à la propriété de remplacement d'observer quand la valeur de propriété sous-jacente change.

riyasavla
la source
7

Dans Swift, ce n'est malheureusement pas possible de le faire. La meilleure alternative est la suivante:

class Jedi {
    private(set) var lightsaberColor = "Blue"
}


class Sith: Jedi {
    override var lightsaberColor : String {
        get {
            return "Red"
        }
    }
}
Noah Wilder
la source
3

J'ai eu le même problème pour définir une constante pour un contrôleur de vue.

Comme j'utilise le générateur d'interface pour gérer la vue, je ne peux pas l'utiliser init(), donc ma solution de contournement était similaire à d'autres réponses, sauf que j'ai utilisé une variable calculée en lecture seule sur les classes de base et héritées.

class Jedi {
    var type: String {
        get { return "Blue" }
    }
}

class Sith: Jedi {
    override var type: String {
        get { return "Red" }
    }
}
Yassine ElBadaoui
la source
3

Si vous essayez de faire cela dans Swift 5, vous serez accueilli par un

Impossible de remplacer la propriété immuable 'let' 'lightSaberColor' avec le getter d'un 'var'

Votre meilleur pari est de le déclarer comme propriété calculée.

Cela fonctionne car nous remplaçons simplement la get {}fonction

class Base {
   var lightSaberColor: String { "base" }
}

class Red: Base {
   override var lightSaberColor: String { "red" }
}
Hugo Alonso
la source
2

Swift ne vous permet pas de remplacer une variable stored propertyAu lieu de cela, vous pouvez utilisercomputed property

class A {
    var property1 = "A: Stored Property 1"

    var property2: String {
        get {
            return "A: Computed Property 2"
        }
    }

    let property3 = "A: Constant Stored Property 3"

    //let can not be a computed property
    
    func foo() -> String {
        return "A: foo()"
    }
}

class B: A {

    //now it is a computed property
    override var property1: String {

        set { }
        get {
            return "B: overrode Stored Property 1"
        }
    }

    override var property2: String {
        get {
            return "B: overrode Computed Property 2"
        }
    }
    
    override func foo() -> String {
        return "B: foo()"
    }

    //let can not be overrode
}
func testPoly() {
    let a = A()
    
    XCTAssertEqual("A: Stored Property 1", a.property1)
    XCTAssertEqual("A: Computed Property 2", a.property2)
    
    XCTAssertEqual("A: foo()", a.foo())
    
    let b = B()
    XCTAssertEqual("B: overrode Stored Property 1", b.property1)
    XCTAssertEqual("B: overrode Computed Property 2", b.property2)
    
    XCTAssertEqual("B: foo()", b.foo())
    
    //B cast to A
    XCTAssertEqual("B: overrode Stored Property 1", (b as! A).property1)
    XCTAssertEqual("B: overrode Computed Property 2", (b as! A).property2)
    
    XCTAssertEqual("B: foo()", (b as! A).foo())
}

C'est plus clair par rapport à Java, où un champ de classe ne peut pas être remplacé et ne prend pas en charge le polymorphisme car il est défini au moment de la compilation (s'exécute efficacement). C'est ce qu'on appelle une variable se cachant [À propos] Il n'est pas recommandé d'utiliser cette technique car elle est difficile à lire / à supporter

[Propriété Swift]

yoAlex5
la source
1

Vous pouvez également utiliser une fonction pour remplacer. Ce n'est pas une réponse directe, mais peut enrichir ce sujet)

Classe A

override func viewDidLoad() {
    super.viewDidLoad()

    if shouldDoSmth() {
       // do
    }
}

public func shouldDoSmth() -> Bool {
    return true
}

Classe B: A

public func shouldDoSmth() -> Bool {
    return false
}
Nik Kov
la source