Zastępowanie przechowywanej właściwości w języku Swift


126

Zauważyłem, że kompilator nie pozwala mi zastąpić przechowywanej właściwości inną przechowywaną wartością (co wydaje się dziwne):

class Jedi {
    var lightSaberColor = "Blue"
}


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

Jednak mogę to zrobić z obliczoną właściwością:

class Jedi {
    let lightSaberColor = "Blue"
}


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

}

Dlaczego nie mogę nadać mu innej wartości?

Dlaczego nadpisywanie przechowywanej własności jest obrzydliwością, a robienie tego z obliczoną wartością koszerną? Co oni myślą?


Odpowiedzi:


82

Dlaczego nie wolno mi po prostu nadać mu innej wartości?

Zdecydowanie możesz nadać odziedziczonej własności inną wartość. Możesz to zrobić, jeśli zainicjujesz właściwość w konstruktorze, który przyjmuje tę wartość początkową i przekażesz inną wartość z klasy pochodnej:

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)

Przesłonięcie właściwości to nie to samo, co nadanie jej nowej wartości - bardziej przypomina nadanie klasie innej właściwości. W rzeczywistości dzieje się tak, gdy zastępujesz obliczoną właściwość: kod, który oblicza właściwość w klasie bazowej, jest zastępowany kodem, który oblicza przesłonięcie dla tej właściwości w klasie pochodnej.

[Czy] można przesłonić rzeczywistą przechowywaną właściwość, tj. lightSaberColorMa inne zachowanie?

Oprócz obserwatorów przechowywane właściwości nie mają zachowania, więc tak naprawdę nie ma nic do zastąpienia. Nadanie nieruchomości innej wartości jest możliwe dzięki mechanizmowi opisanemu powyżej. Robi to dokładnie to, co przykład w pytaniu próbuje osiągnąć, z inną składnią.


2
@MaxMacLeod Oprócz obserwatorów, przechowywane właściwości nie mają zachowania, więc tak naprawdę nie ma nic do zastąpienia. Chciał nadać przechowywanej właściwości inną wartość w podklasie, ale nie był pewien mechanizmu, który to umożliwia. Odpowiedź wyjaśnia, jak można to zrobić w języku Swift. Przepraszam za spóźnioną odpowiedź, wygląda na to, że Twój komentarz wywołuje dość zamieszania, aby przyciągnąć głosy przeciwne, więc postanowiłem wyjaśnić, o co chodzi.
dasblinkenlight

55

Dla mnie twój przykład nie działa w Swift 3.0.1.

Na placu zabaw wpisałem ten kod:

class Jedi {
    let lightsaberColor = "Blue"
}

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

Zgłasza błąd podczas kompilacji w Xcode:

nie można przesłonić niezmiennej właściwości „let” „lightsaberColor” za pomocą metody pobierającej wartość „var”

Nie, nie możesz zmienić typu przechowywanej właściwości. Zasada Zastępstwa Liskova zmusza cię do zezwolenia na użycie podklasy w miejscu, w którym potrzebna jest nadklasa.

Ale jeśli zmienisz to na vari w związku z tym dodasz setwłaściwość obliczoną, możesz zastąpić właściwość przechowywaną właściwością obliczoną tego samego typu.

class Jedi {
    var lightsaberColor = "Blue"
}


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

Jest to możliwe, ponieważ przełączenie się z właściwości przechowywanej na właściwość obliczoną może mieć sens.

Jednak zastąpienie varwłaściwości przechowywanej właściwością przechowywaną varnie ma sensu, ponieważ można zmienić tylko wartość właściwości.

Nie można jednak w ogóle zastąpić właściwości przechowywanej właściwością przechowywaną.


Nie powiedziałbym, że Sithowie są Jedi :-P. Dlatego jasne jest, że to nie może działać.


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

13
Chociaż ten fragment kodu może rozwiązać problem, dołączenie wyjaśnienia naprawdę pomaga poprawić jakość Twojego posta. Pamiętaj, że odpowiadasz na pytanie do czytelników w przyszłości, a osoby te mogą nie znać powodów, dla których zaproponowałeś kod.
DimaSan

15

Prawdopodobnie chcesz przypisać inną wartość właściwości:

class Jedi {
    var lightSaberColor = "Blue"
}


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

9
to samo dotyczy komentarza powyżej
Max MacLeod

10

W przypadku Swift 4 z dokumentacji Apple :

Można przesłonić dziedziczone wystąpienie lub właściwość typu, aby zapewnić własny niestandardowy moduł pobierający i ustawiający dla tej właściwości lub dodać obserwatory właściwości, aby umożliwić zastępowanie właściwości obserwowanie, gdy zmienia się podstawowa wartość właściwości.


7

W Swift nie jest to niestety możliwe. Najlepsza alternatywa to:

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


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

3

Miałem ten sam problem, aby ustawić stałą dla kontrolera widoku.

Ponieważ używam konstruktora interfejsu do zarządzania widokiem, nie mogę go używać init(), więc moje obejście było podobne do innych odpowiedzi, z wyjątkiem tego, że użyłem obliczonej zmiennej tylko do odczytu zarówno dla klas podstawowych, jak i dziedziczonych.

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

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

3

Jeśli spróbujesz to zrobić w Swift 5, zostaniesz powitany przez

Nie można zastąpić niezmiennej właściwości „let” „lightSaberColor” metodą pobierającą wartość „var”

Najlepszym rozwiązaniem jest zadeklarowanie tego jako obliczonej właściwości.

Działa to, ponieważ właśnie zastępujemy get {}funkcję

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

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

2

Swift nie pozwala na przesłonięcie zmiennej stored property Zamiast tego możesz użyćcomputed 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())
}

Jest to bardziej czytelne w porównaniu z Javą, gdzie pole klasy nie może być nadpisane i nie obsługuje polimorfizmu, ponieważ jest zdefiniowane w czasie kompilacji (działa wydajnie). Nazywa się to ukrywaniem zmiennej [Informacje]. Nie zaleca się korzystania z tej techniki, ponieważ jest ona trudna do odczytania / obsługi

[Swift property]


1

Możesz również użyć funkcji do przesłonięcia. To nie jest bezpośrednia odpowiedź, ale może wzbogacić ten temat)

Klasa A

override func viewDidLoad() {
    super.viewDidLoad()

    if shouldDoSmth() {
       // do
    }
}

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

Klasa B: A

public func shouldDoSmth() -> Bool {
    return false
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.