Mengganti properti superclass dengan tipe berbeda di Swift

129

Dalam Swift, dapatkah seseorang menjelaskan cara menimpa properti pada superclass dengan objek lain yang disubklasifikasikan dari properti asli?

Ambil contoh sederhana ini:

class Chassis {}
class RacingChassis : Chassis {}

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

Ini memberikan kesalahan:

Cannot override with a stored property 'chassis'

Jika saya memiliki sasis sebagai 'var', saya mendapatkan kesalahan:

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

Satu-satunya hal yang dapat saya temukan dalam panduan di bawah "Overriding Properties" menunjukkan bahwa kita harus mengganti pengambil dan penyetel, yang mungkin berfungsi untuk mengubah nilai properti (jika itu 'var'), tetapi bagaimana dengan mengubah kelas properti ?

James
sumber

Jawaban:

115

Swift tidak memungkinkan Anda untuk mengubah tipe kelas dari variabel atau properti apa pun. Sebagai gantinya, Anda dapat membuat variabel tambahan di subkelas yang menangani tipe kelas baru:

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

Tampaknya seseorang tidak dapat mendeklarasikan properti dengan sintaks let dan menimpanya dengan var di subkelasnya atau sebaliknya, yang mungkin karena implementasi superclass mungkin tidak mengharapkan properti itu berubah setelah diinisialisasi. Jadi dalam hal ini properti perlu dideklarasikan dengan 'var' di superclass juga agar sesuai dengan subclass (seperti yang ditunjukkan dalam snippet di atas). Jika seseorang tidak dapat mengubah kode sumber di superclass maka mungkin yang terbaik untuk menghancurkan RaceCar saat ini dan membuat RaceCar baru setiap kali sasis perlu dimutasi.

Berlari
sumber
1
Maaf baru saja menghapus komentar saya dan kemudian melihat balasan Anda. Saya mengalami masalah karena dalam kasus nyata saya superclass saya adalah kelas Objective-C dengan strongproperti dan saya mendapatkan kesalahan saat mencoba menimpanya - tetapi sepertinya saya melewatkan bahwa itu diterjemahkan ke "opsional yang tidak terbuka secara implisit" ( chassis!) di Cepat, jadi override var chassis : Chassis!perbaiki.
James
4
Ini tidak lagi berfungsi dengan Swift 1.1 di bawah Xcode 6.1. Menghasilkan kesalahan: "Tidak dapat mengesampingkan 'biarkan' properti 'chassis' dengan pengambil 'var'". Ada ide untuk solusi yang lebih baik?
Darrarski
1
Juga tidak lagi berfungsi di Swift 1.2 di bawah Xcode 6.3 beta. Tidak dapat mengesampingkan properti yang dapat berubah 'A' dari tipe 'Type1' dengan tipe covariant '
Type2
10
Ini benar-benar lumpuh, dan kegagalan Swift, imo. Karena RacingChassis adalah Chassis, kompiler seharusnya tidak memiliki masalah yang memungkinkan Anda untuk memperbaiki kelas properti dalam subkelas. Banyak bahasa memungkinkan ini, dan tidak mendukungnya menyebabkan penyelesaian yang buruk seperti ini. Tanpa bermaksud menyinggung. : /
devios1
1
Opsional memberikan cara untuk menyarankan bahwa suatu variabel mungkin tidak hadir karena disediakan oleh fungsi yang tidak lagi mengembalikan nilai yang diharapkan. Kasus seperti itu dapat terjadi jika fungsi tersebut diganti. Lihatlah jawaban saya untuk detailnya.
10

Ini sepertinya berhasil

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
sumber
1
Ini berfungsi sebagai properti sasis didefinisikan sebagai letdi dalam Carkelas yang membuatnya tidak mungkin untuk diubah. Kami hanya dapat mengubah chassisproperti di RaceCarkelas.
David Arve
4
Ini tidak berfungsi untuk Swift 2.0. Ini memberikan "kesalahan: tidak dapat mengesampingkan 'biarkan' properti 'chasis' dengan pengambil 'var'"
mohamede1945
Ini tidak bekerja di swift 4.0 juga. eksekusi layground gagal: error: MyPlaygrounds.playground: 14: 15: error: tidak dapat mengesampingkan 'biarkan' properti 'sasis' dengan pengambil 'var' mengesampingkan sasis var: RacingChassis {^ MyPlaygrounds.playground: 11: 6: note : mencoba untuk menimpa properti di sini biarkan sasis = Chassis () ^
karim
7

Coba ini:

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

Kemudian:

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

Detail dalam http://www.mylonly.com/14957025459875.html

adimtxg0422
sumber
Ah rapi dan bersih
Inder Kumar Rathore
3

Solusi Dash yang disediakan berfungsi dengan baik kecuali bahwa kelas super harus dideklarasikan dengan kata kunci let dan bukan var. Berikut ini adalah solusi yang mungkin tetapi TIDAK DIANJURKAN!

Solusi di bawah ini akan dikompilasi dengan Xcode 6.2, SWIFT 1.1 (jika semua kelas dalam file swift yang berbeda) tetapi harus dihindari karena ITU BISA MENUJU PERILAKU YANG TAK TERKENAL (TERMASUK CRASH, terutama ketika menggunakan tipe non-opsional). CATATAN: INI TIDAK BEKERJA DENGAN 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
sumber
1
Itu tidak bekerja untuk Swift 2.0. Ini memberi "tidak bisa mengesampingkan 'sasis' properti yang dapat diubah dari tipe 'Sasis?' dengan tipe kovarian 'RacingChassis?' "
mohamede1945
2

Secara teoritis, Anda diizinkan melakukannya dengan cara ini ...

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

Ini berfungsi dengan baik di taman bermain Xcode.

Tetapi , jika Anda mencoba ini di proyek nyata, kesalahan kompiler memberitahu Anda:

'Tampilan' deklarasi tidak dapat menggantikan lebih dari satu deklarasi superclass

Saya hanya memeriksa Xcode 6.0 GM sampai sekarang.

Sayangnya, Anda harus menunggu sampai Apple memperbaikinya.

Saya telah mengirimkan laporan bug juga. 18518795

aleclarson
sumber
Ini adalah solusi yang menarik, dan bekerja di 6.1.
Chris Conover
1

Saya telah melihat banyak alasan mengapa mendesain API menggunakan variabel alih-alih fungsi itu bermasalah dan bagi saya menggunakan properti yang dikomputasi terasa seperti solusi. Ada alasan bagus untuk menjaga agar variabel instan Anda tetap dienkapsulasi. Di sini saya telah membuat protokol Automobile yang sesuai dengan Mobil. Protokol ini memiliki metode accessor yang mengembalikan objek Chassis. Karena Mobil sesuai dengannya, subkelas RaceCar dapat menimpanya dan mengembalikan subkelas Chassis yang berbeda. Ini memungkinkan kelas Mobil memprogram ke antarmuka (Otomotif) dan kelas RaceCar yang tahu tentang RacingChassis dapat mengakses variabel _racingChassis secara langsung.

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

Contoh lain mengapa mendesain API menggunakan variabel rusak adalah ketika Anda memiliki variabel dalam protokol. Jika Anda ingin membagi semua fungsi protokol menjadi ekstensi yang Anda bisa, kecuali properti yang disimpan tidak dapat ditempatkan dalam ekstensi dan harus didefinisikan di kelas (untuk mendapatkan ini untuk dikompilasi Anda harus membatalkan komentar kode di Kelas AdaptableViewController dan hapus variabel mode dari ekstensi):

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

Kode di atas akan memiliki kesalahan kompiler ini: "Ekstensi mungkin tidak memiliki properti yang disimpan". Inilah cara Anda dapat menulis ulang contoh di atas sehingga segala sesuatu dalam protokol dapat dipisahkan dalam ekstensi dengan menggunakan fungsi sebagai gantinya:

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

class AdaptableViewController: UIViewController {
}

extension AdaptableViewController: Adaptable {
    func mode() -> Int {
        return 0
    }
    func adapt() {
        // adapt code
    }
}
Korey Hinton
sumber
1

Anda dapat mencapainya dengan menggunakan obat generik:

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

Dengan pendekatan ini Anda akan memiliki 100% jenis kode aman tanpa paksaan membuka.

Tetapi ini adalah jenis peretasan, dan jika Anda perlu mendefinisikan ulang beberapa properti daripada deklarasi kelas Anda akan terlihat seperti kekacauan total. Jadi berhati-hatilah dengan pendekatan ini.

Al Zonke
sumber
1

Bergantung pada bagaimana Anda berencana menggunakan properti, cara paling sederhana untuk melakukan ini adalah dengan menggunakan tipe opsional untuk subkelas Anda, dan mengganti didSet {}metode untuk 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
        }
    }
}

Jelas Anda perlu meluangkan waktu memeriksa untuk memastikan kelas dapat diinisialisasi dengan cara ini, tetapi dengan mengatur properti menjadi opsional, Anda melindungi diri sendiri terhadap situasi di mana casting tidak lagi berfungsi.

naskah merek
sumber
0

Anda cukup membuat variabel lain dari RacingChassis.

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

class RaceCar: Car {
let raceChassis: RacingChassis
init(){
        raceChassis = RacingChassis()
}}
Anton
sumber
Ini berfungsi, tapi saya pikir jawaban Dash mengambil satu langkah lebih jauh dengan mengesampingkan chassisdan memungkinkan kita untuk mendapatkan / mengatur RacingChassiscontoh kita dengan chassisproperti.
James
0

Coba ini:

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()
    }
}
Nasi goreng
sumber
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
sumber
0

Berikut ini memungkinkan objek tunggal untuk digunakan dalam kelas dasar dan turunan. Di kelas turunan, gunakan properti objek turunan.

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
sumber
0

Variasi sedikit dari jawaban lain, tetapi lebih sederhana dan lebih aman dengan beberapa manfaat yang bagus.

class Chassis {}
class RacingChassis : Chassis {}

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

Manfaatnya termasuk bahwa tidak ada batasan pada apa sasis harus (var, biarkan, opsional, dll), dan mudah untuk subkelas RaceCar. Subclass dari RaceCar kemudian dapat memiliki nilai yang dihitung sendiri untuk sasis (atau racingChassis).

Ryan H
sumber
-2

cukup tetapkan properti imageview baru dengan konvensi penamaan yang berbeda seperti imgview karena imageView sudah menjadi miliknya sendiri dan kami tidak dapat menetapkan 2 properti kuat.

Ali Rafiq
sumber