Sostituzione di una proprietà memorizzata in Swift


126

Ho notato che il compilatore non mi consente di sovrascrivere una proprietà memorizzata con un altro valore memorizzato (che sembra strano):

class Jedi {
    var lightSaberColor = "Blue"
}


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

Tuttavia, posso farlo con una proprietà calcolata:

class Jedi {
    let lightSaberColor = "Blue"
}


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

}

Perché non posso dargli un altro valore?

Perché ignorare una proprietà immagazzinata è un abominio e farlo con un kosher calcolato? Cosa dove stanno pensando?


Risposte:


82

Perché non mi è permesso dargli un altro valore?

È sicuramente consentito dare a una proprietà ereditata un valore diverso. Puoi farlo se inizializzi la proprietà in un costruttore che accetta quel valore iniziale e passi un valore diverso dalla classe derivata:

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)

Sovrascrivere una proprietà non è la stessa cosa che darle un nuovo valore: è più come dare a una classe una proprietà diversa. In effetti, questo è ciò che accade quando si sovrascrive una proprietà calcolata: il codice che calcola la proprietà nella classe base viene sostituito dal codice che calcola l'override per quella proprietà nella classe derivata.

[È] possibile sovrascrivere l'effettiva proprietà memorizzata, cioè lightSaberColorche ha qualche altro comportamento?

A parte gli osservatori, le proprietà memorizzate non hanno un comportamento, quindi non c'è davvero nulla da sovrascrivere. Dare all'immobile un diverso valore è possibile attraverso il meccanismo sopra descritto. Questo fa esattamente ciò che l'esempio nella domanda sta cercando di ottenere, con una sintassi diversa.


2
@MaxMacLeod A parte gli osservatori, le proprietà memorizzate non hanno un comportamento, quindi non c'è davvero nulla da sovrascrivere. Voleva dare a una proprietà memorizzata un valore diverso nella sottoclasse, ma non era sicuro del meccanismo per ottenerlo. La risposta spiega come farlo in Swift. Mi dispiace per la risposta tardiva, sembra che il tuo commento crei abbastanza confusione da attirare voti negativi, quindi ho deciso di spiegare cosa sta succedendo.
dasblinkenlight

55

Per me, il tuo esempio non funziona in Swift 3.0.1.

Ho inserito nel playground questo codice:

class Jedi {
    let lightsaberColor = "Blue"
}

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

Genera un errore in fase di compilazione in Xcode:

non può sovrascrivere la proprietà immutabile "let" "lightsaberColor" con il getter di un "var"

No, non è possibile modificare il tipo di proprietà memorizzata. Il principio di sostituzione di Liskov ti obbliga a consentire che una sottoclasse venga utilizzata in un luogo in cui si desidera la superclasse.

Ma se lo si modifica in vare quindi si aggiunge setnella proprietà calcolata, è possibile sovrascrivere la proprietà memorizzata con una proprietà calcolata dello stesso tipo.

class Jedi {
    var lightsaberColor = "Blue"
}


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

Ciò è possibile perché può avere senso passare dalla proprietà memorizzata alla proprietà calcolata.

Ma sostituire una varproprietà archiviata con una varproprietà archiviata non ha senso, perché è possibile modificare solo il valore della proprietà.

Tuttavia, non è possibile sovrascrivere affatto una proprietà archiviata con una proprietà archiviata.


Non direi che i Sith sono Jedi :-P. Quindi è chiaro che questo non può funzionare.


18
class SomeClass {
    var hello = "hello"
}
class ChildClass: SomeClass {
    override var hello: String {
        set {
            super.hello = newValue
        }
        get {
            return super.hello
        }    
    }
}

13
Sebbene questo snippet di codice possa risolvere la domanda, includere una spiegazione aiuta davvero a migliorare la qualità del tuo post. Ricorda che stai rispondendo alla domanda per i lettori in futuro e quelle persone potrebbero non conoscere i motivi del tuo suggerimento sul codice.
DimaSan

15

Probabilmente vuoi assegnare un altro valore alla proprietà:

class Jedi {
    var lightSaberColor = "Blue"
}


class Sith: Jedi {
    override init() {
        super.init()
        self.lightSaberColor = "Red"
    }
}

9
lo stesso vale per il commento sopra
Max MacLeod

10

Per Swift 4, dalla documentazione di Apple :

È possibile sovrascrivere un'istanza ereditata o una proprietà di tipo per fornire il proprio getter e setter personalizzato per quella proprietà oppure aggiungere osservatori di proprietà per consentire alla proprietà di sovrascrittura di osservare quando cambia il valore della proprietà sottostante.


7

In Swift, questo purtroppo non è possibile. La migliore alternativa è la seguente:

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


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

3

Ho avuto lo stesso problema nell'impostare una costante per un controller di visualizzazione.

Poiché sto utilizzando il generatore di interfacce per gestire la vista, non posso utilizzarlo init(), quindi la mia soluzione alternativa era simile ad altre risposte, tranne per il fatto che ho utilizzato una variabile calcolata di sola lettura su entrambe le classi di base ed ereditate.

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

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

3

Se provi a farlo in Swift 5, sarai accolto da un file

Impossibile sovrascrivere la proprietà immutabile "let" "lightSaberColor" con il getter di una "var"

La soluzione migliore è dichiararla come proprietà calcolata.

Funziona poiché stiamo solo sovrascrivendo la get {}funzione

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

class Red: Base {
   override var lightSaberColor: String { "red" }
}

2

Swift non ti consente di sovrascrivere una variabile. stored propertyInvece di questa puoi usarecomputed 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())
}

È più chiaro se confrontato con Java, dove un campo di classe non può essere sovrascritto e non supporta il polimorfismo perché è definito in fase di compilazione (eseguito in modo efficiente). Si chiama una variabile che nasconde [Informazioni su] Non è consigliabile utilizzare questa tecnica perché è difficile da leggere / supportare

[Proprietà Swift]


1

È inoltre possibile utilizzare una funzione per eseguire l'override. Non è una risposta diretta, ma può arricchire questo argomento)

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
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.