Zastępowanie właściwości nadklasy innym typem w języku Swift

129

Czy w języku Swift ktoś może wyjaśnić, jak zastąpić właściwość klasy nadrzędnej innym obiektem podklasy z właściwości oryginalnej?

Weźmy ten prosty przykład:

class Chassis {}
class RacingChassis : Chassis {}

class Car {
    let chassis = Chassis()
}
class RaceCar: Car {
    override let chassis = RacingChassis() //Error here
}

To daje błąd:

Cannot override with a stored property 'chassis'

Jeśli zamiast tego mam podwozie jako `` var '', pojawia się błąd:

Cannot override mutable property 'chassis' of type 'Chassis' with covariant type 'RacingChassis'

Jedyne, co udało mi się znaleźć w przewodniku w sekcji „Przesłanianie właściwości”, wskazuje, że musimy przesłonić metody pobierające i ustawiające, które mogą działać przy zmianie wartości właściwości (jeśli jest to „zmienna”), ale co ze zmianą klasy właściwości ?

James
źródło

Odpowiedzi:

115

Swift nie pozwala na zmianę typu klasy żadnych zmiennych ani właściwości. Zamiast tego możesz utworzyć dodatkową zmienną w podklasie, która obsługuje nowy typ klasy:

class Chassis {}
class RacingChassis : Chassis {}

class Car {
    var chassis = Chassis()
}
class RaceCar: Car {
    var racingChassis = RacingChassis()
    override var chassis: Chassis {
        get {
            return racingChassis
        }
        set {
            if let newRacingChassis = newValue as? RacingChassis {
                racingChassis = newRacingChassis
            } else {
                println("incorrect chassis type for racecar")
            }
        }
    }
}

Wydaje się, że nie można zadeklarować właściwości ze składnią let i przesłonić ją zmienną w jej podklasie lub odwrotnie, co może być spowodowane tym, że implementacja nadklasy może nie oczekiwać, że ta właściwość ulegnie zmianie po zainicjowaniu. Zatem w tym przypadku właściwość musi zostać zadeklarowana za pomocą „var” w nadklasie, aby pasowała do podklasy (jak pokazano w powyższym fragmencie). Jeśli nie można zmienić kodu źródłowego w superklasie, prawdopodobnie najlepiej będzie zniszczyć obecny RaceCar i stworzyć nowy RaceCar za każdym razem, gdy konieczne będzie zmutowanie podwozia.

Dziarskość
źródło
1
Przepraszam, właśnie usunąłem mój komentarz i zobaczyłem twoją odpowiedź. Miałem problem, ponieważ w moim prawdziwym przypadku moja nadklasa jest klasą Objective-C z strongwłaściwością i otrzymywałem błąd podczas próby jej przesłonięcia - ale wygląda na to, że przeoczyłem, że przekłada się to na „niejawnie rozpakowany opcjonalny” ( chassis!) w Szybki, więc override var chassis : Chassis!naprawia to.
James,
4
To już nie działa ze Swift 1.1 w Xcode 6.1. Generuje błąd: „Nie można przesłonić niezmiennej właściwości 'let' chassis 'za pomocą metody pobierającej' var '”. Jakieś pomysły na lepsze rozwiązanie?
Darrarski
1
Również nie działa już w Swift 1.2 pod Xcode 6.3 beta. Nie można zastąpić mutowalnej właściwości „A” typu „Type1” kowariantem typu „Type2”
bubuxu
10
To jest naprawdę kiepskie i zawodzi Swift, imo. Ponieważ RacingChassis to Chassis, kompilator nie powinien mieć problemu z dopracowaniem klasy właściwości w podklasie. Wiele języków na to pozwala, a brak wsparcia prowadzi do takich brzydkich obejść. Bez urazy. : /
devios1
1
Opcje pozwalają zasugerować, że zmienna może nie być obecna, ponieważ została dostarczona przez funkcję, która nie zwraca już oczekiwanej wartości. Takie przypadki mogą się zdarzyć, jeśli funkcja została nadpisana. Spójrz na moją odpowiedź, aby poznać szczegóły.
10

To wydaje się działać

class Chassis {
    func description() -> String {
        return "Chassis"
    }
}
class RacingChassis : Chassis {
    override func description() -> String {
        return "Racing Chassis"
    }

    func racingChassisMethod() -> String {
        return "Wrooom"
    }
}

class Car {
    let chassis = Chassis()
}
class RaceCar: Car {
    override var chassis: RacingChassis {
    get {
        return self.chassis
    }
    set {
        self.chassis = newValue
    }
    }
}

var car = Car()
car.chassis.description()

var raceCar = RaceCar()
raceCar.chassis.description()
raceCar.chassis.racingChassisMethod()
David Arve
źródło
1
Działa to, ponieważ właściwość podwozia jest zdefiniowana jak letw Carklasie, która uniemożliwia zmianę. Możemy zmienić chassiswłaściwość tylko w RaceCarklasie.
David Arve,
4
To nie działa w przypadku Swift 2.0. Daje "błąd: nie można nadpisać niezmiennej własności 'let' property 'chassis' z getter z 'var'”
mohamede1945
To nie działa również w Swift 4.0. wykonanie layground nie powiodło się: błąd: MyPlaygrounds.playground: 14: 15: błąd: nie można nadpisać niezmiennej właściwości „let” podwozia ”za pomocą funkcji pobierającej zmienną„ var ”zastępuje zmienną obudowę: RacingChassis {^ MyPlaygrounds.playground: 11: 6: uwaga : próba zastąpienia właściwości tutaj niech chassis = Chassis () ^
karim
7

Spróbuj tego:

class Chassis{
     var chassis{
         return "chassis"
     } 
}

class RacingChassis:Chassis{
     var racing{
         return "racing"
     } 
}

class Car<Type:Chassis> {
     let chassis: Type
     init(chassis:Type){
        self.chassis = chassis
     }
}

class RaceCar: Car<RacingChassis> {
     var description{
         return self.chassis.racing
     } 
}

Następnie:

let racingChassis = RacingChassis()
let raceCar = RaceCar(chassis:racingChassis)
print(raceCar.description) //output:racing

Szczegóły w http://www.mylonly.com/14957025459875.html

adimtxg0422
źródło
Ach schludnie i czysto
Inder Kumar Rathore
3

Dostarczony przez Solution Dash działa dobrze, z tym wyjątkiem, że superklasa musi być zadeklarowana za pomocą słowa kluczowego let, a nie var. Oto rozwiązanie, które jest możliwe, ale NIE ZALECANE!

Poniższe rozwiązanie skompiluje się z Xcode 6.2, SWIFT 1.1 (jeśli wszystkie klasy znajdują się w różnych plikach Swift), ale należy go unikać, ponieważ MOŻE PROWADZIĆ DO NIEOCZEKIWANYCH ZACHOWAŃ (W TYM AWARII, zwłaszcza przy użyciu typów nieobowiązkowych). UWAGA: TO NIE DZIAŁA Z XCODE 6.3 BETA 3, SWIFT 1.2

class Chassis {}
class RacingChassis : Chassis {}
class Car {
    var chassis:Chassis? = Chassis()
}

class RaceCar: Car {
    override var chassis: RacingChassis? {
        get {
            return super.chassis as? RacingChassis
        }
        set {
            super.chassis = newValue
        }
    }
}
Lukman
źródło
1
Nie działa w przypadku Swift 2.0. Daje „nie można zastąpić zmiennej„ podwozie ”typu„ Podwozie? ” z kowariantnym typem „RacingChassis?” ”
mohamede1945
2

Teoretycznie możesz to zrobić w ten sposób ...

class ViewController {

    var view: UIView! { return _view }

    private var _view: UIView!
}

class ScrollView : UIView {}

class ScrollViewController : ViewController {

    override var view: ScrollView! { return super.view as ScrollView! }
}

class HomeView : ScrollView {}

class HomeViewController : ScrollViewController {

    override var view: HomeView! { return super.view as HomeView! }
}

Działa to doskonale na placu zabaw Xcode.

Ale jeśli spróbujesz tego w prawdziwym projekcie, błąd kompilatora powie ci:

Deklaracja „widok” nie może przesłaniać więcej niż jednej deklaracji nadklasy

Sprawdziłem tylko Xcode 6.0 GM.

Niestety będziesz musiał poczekać, aż Apple to naprawi.

Przesłałem też raport o błędzie. 18518795

aleclarson
źródło
To ciekawe rozwiązanie, które działa w 6.1.
Chris Conover
1

Widziałem wiele powodów, dla których projektowanie API przy użyciu zmiennych zamiast funkcji jest problematyczne, a używanie obliczonych właściwości wydaje mi się obejściem. Istnieją dobre powody, aby zachować hermetyzację zmiennych instancji. Tutaj stworzyłem protokół Samochód, z którym jest zgodny samochód. Ten protokół ma metodę akcesora, która zwraca obiekt Chassis. Ponieważ Car jest z nim zgodny, podklasa RaceCar może go zastąpić i zwrócić inną podklasę Chassis. Pozwala to klasie Car na programowanie w interfejsie (Automobile), a klasie RaceCar, która wie o RacingChassis, może uzyskać bezpośredni dostęp do zmiennej _racingChassis.

class Chassis {}
class RacingChassis: Chassis {}

protocol Automobile {
    func chassis() -> Chassis
}

class Car: Automobile {
    private var _chassis: Chassis

    init () {
        _chassis = Chassis()
    }

    func chassis() -> Chassis {
        return _chassis
    }
}

class RaceCar: Car {
    private var _racingChassis: RacingChassis

    override init () {
        _racingChassis = RacingChassis()
        super.init()
    }

    override func chassis() -> Chassis {
        return _racingChassis
    }
}

Innym przykładem załamania się projektowania interfejsu API przy użyciu zmiennych jest sytuacja, gdy w protokole są zmienne. Jeśli chcesz rozbić wszystkie funkcje protokołu na rozszerzenia, które możesz, z wyjątkiem tego, że przechowywane właściwości nie mogą być umieszczane w rozszerzeniach i muszą być zdefiniowane w klasie (aby to skompilować, musisz odkomentować kod w AdaptableViewController i usuń zmienną trybu z rozszerzenia):

protocol Adaptable {
    var mode: Int { get set }
    func adapt()
}

class AdaptableViewController: UIViewController {
    // var mode = 0
}

extension AdaptableViewController: Adaptable {

    var mode = 0 // compiler error

    func adapt() {
        //TODO: add adapt code
    }
}

Powyższy kod będzie miał następujący błąd kompilatora: „Rozszerzenia mogą nie mieć zapisanych właściwości”. Oto, jak możesz ponownie napisać powyższy przykład, aby wszystko w protokole można było oddzielić w rozszerzeniu za pomocą zamiast tego funkcji:

protocol Adaptable {
    func mode() -> Int
    func adapt()
}

class AdaptableViewController: UIViewController {
}

extension AdaptableViewController: Adaptable {
    func mode() -> Int {
        return 0
    }
    func adapt() {
        // adapt code
    }
}
Korey Hinton
źródło
1

Możesz to osiągnąć za pomocą generyków:

class Descriptor {
    let var1 = "a"
}

class OtherDescriptor: Descriptor {
    let var2 = "b"
}

class Asset<D: Descriptor> {
    let descriptor: D

    init(withDescriptor descriptor: D) {
        self.descriptor = descriptor
    }

    func printInfo() {
        print(descriptor.var1)
    }
}

class OtherAsset<D: OtherDescriptor>: Asset<D> {
    override func printInfo() {
        print(descriptor.var1, descriptor.var2)
    }
}

let asset = Asset(withDescriptor: Descriptor())
asset.printInfo() // a

let otherAsset = OtherAsset(withDescriptor: OtherDescriptor())
otherAsset.printInfo() // a b

Dzięki takiemu podejściu uzyskasz 100% bezpieczny kod bez wymuszonego rozpakowywania.

Ale to trochę hack i jeśli będziesz musiał przedefiniować kilka właściwości, to deklaracje klas będą wyglądać jak totalny bałagan. Więc bądź ostrożny z tym podejściem.

Al Zonke
źródło
1

W zależności od tego, jak planujesz używać właściwości, najprostszym sposobem na to jest użycie opcjonalnego typu dla swojej podklasy i zastąpienie didSet {}metody super:

class Chassis { }
class RacingChassis: Chassis { }

class Car {
    // Declare this an optional type, and do your 
    // due diligence to check that it's initialized
    // where applicable
    var chassis: Chassis?
}
class RaceCar: Car {
    // The subclass is naturally an optional too
    var racingChassis: RacingChassis?
    override var chassis: Chassis {
        didSet {
            // using an optional, we try to set the type
            racingChassis = chassis as? RacingChassis
        }
    }
}

Oczywiście musisz poświęcić trochę czasu na sprawdzenie, czy klasy można zainicjować w ten sposób, ale ustawiając właściwości na opcjonalne, chronisz się przed sytuacjami, w których rzutowanie już nie działa.

brandonscript
źródło
0

Możesz po prostu utworzyć inną zmienną RacingChassis.

class Chassis {}
class RacingChassis : Chassis {}
class Car {
    let chassis: Chassis
    init(){
        chassis = Chassis()
}}

class RaceCar: Car {
let raceChassis: RacingChassis
init(){
        raceChassis = RacingChassis()
}}
Anton
źródło
To działa, ale myślę, że odpowiedź Dasha idzie o krok dalej, zastępując chassisi pozwalając nam pobrać / ustawić naszą RacingChassisinstancję z chassiswłaściwością.
James,
0

Spróbuj tego:

class Chassis {}
class RacingChassis : Chassis {}
class SuperChassis : RacingChassis {}

class Car {
    private var chassis: Chassis? = nil
    func getChassis() -> Chassis? {
        return chassis
    }

    func setChassis(chassis: Chassis) {
        self.chassis = chassis
    }
}

class RaceCar: Car {
    private var chassis: RacingChassis {
        get {
            return getChassis() as! RacingChassis
        }
        set {
            setChassis(chassis: newValue)
        }
    }

    override init() {
        super.init()

        chassis = RacingChassis()
    }
}

class SuperCar: RaceCar {
    private var chassis: SuperChassis {
        get {
            return getChassis() as! SuperChassis
        }
        set {
            setChassis(chassis: newValue)
        }
    }

    override init() {
        super.init()

        chassis = SuperChassis()
    }
}
Smażony ryż
źródło
0
class Chassis {}
class RacingChassis : Chassis {}

class Car {
    fileprivate let theChassis: Chassis
    var chassis: Chassis {
        get {
            return theChassis
        }
    }
    fileprivate init(_ chassis: Chassis) {
        theChassis = chassis
    }
    convenience init() {
        self.init(Chassis())
    }
}
class RaceCar: Car {
    override var chassis: RacingChassis {
        get {
            return theChassis as! RacingChassis
        }
    }
    init() {
        super.init(RacingChassis())
    }
}
Dombi Bence
źródło
0

Poniższe czynności umożliwiają użycie pojedynczego obiektu w klasach bazowych i pochodnych. W klasie pochodnej użyj właściwości obiektu pochodnego.

class Car {
    var chassis:Chassis?

    func inspect() {
        chassis?.checkForRust()
    }
}

class RaceCar: Car {
    var racingChassis: RacingChassis? {
        get {
            return chassis as? RacingChassis
        } 
    }

    override func inspect() {
        super.inspect()
        racingChassis?.tuneSuspension()
    }
}
Stickley
źródło
0

Niewielkie zróżnicowanie innych odpowiedzi, ale prostsze i bezpieczniejsze z kilkoma fajnymi zaletami.

class Chassis {}
class RacingChassis : Chassis {}

class Car {
    let chassis = Chassis()
}
class RaceCar: Car {
    var racingChassis: RacingChassis? {
        get {
            return chassis as? RacingChassis
        }
    }
}

Korzyści obejmują to, że nie ma ograniczeń co do tego, jakie musi być podwozie (var, let, opcjonalne itp.) I łatwo jest podklasować RaceCar. Podklasy RaceCar mogą wtedy mieć własną obliczoną wartość dla podwozia (lub podwozia wyścigowego).

Ryan H.
źródło
-2

wystarczy ustawić nową właściwość imageview z inną konwencją nazewnictwa, na przykład imgview, ponieważ imageView jest już własną własnością i nie możemy przypisać 2 silnych właściwości.

Ali Rafiq
źródło