Ghi đè thuộc tính siêu lớp với loại khác nhau trong Swift


129

Trong Swift, ai đó có thể giải thích cách ghi đè một thuộc tính trên siêu lớp với một đối tượng khác được phân lớp từ thuộc tính ban đầu không?

Lấy ví dụ đơn giản này:

class Chassis {}
class RacingChassis : Chassis {}

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

Điều này đưa ra lỗi:

Cannot override with a stored property 'chassis'

Thay vào đó, nếu tôi có khung gầm là 'var', tôi sẽ gặp lỗi:

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

Điều duy nhất tôi có thể tìm thấy trong hướng dẫn trong "Thuộc tính ghi đè" chỉ ra rằng chúng ta phải ghi đè getter và setter, có thể hoạt động để thay đổi giá trị của thuộc tính (nếu là 'var'), nhưng về việc thay đổi lớp thuộc tính ?

Câu trả lời:


115

Swift không cho phép bạn thay đổi loại lớp của bất kỳ biến hoặc thuộc tính nào. Thay vào đó, bạn có thể tạo một biến phụ trong lớp con xử lý loại lớp mới:

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

Dường như người ta không thể khai báo một thuộc tính bằng cú pháp let và ghi đè lên nó bằng var trong lớp con hoặc ngược lại, điều này có thể là do việc triển khai siêu lớp có thể không mong đợi thuộc tính đó thay đổi khi được khởi tạo. Vì vậy, trong trường hợp này, thuộc tính cần phải được khai báo bằng 'var' trong lớp cha cũng như để khớp với lớp con (như được hiển thị trong đoạn trích ở trên). Nếu người ta không thể thay đổi mã nguồn trong siêu lớp thì có lẽ tốt nhất là phá hủy RaceCar hiện tại và tạo RaceCar mới mỗi khi khung gầm cần được thay đổi.


1
Xin lỗi chỉ cần xóa bình luận của tôi và sau đó thấy trả lời của bạn. Tôi đã gặp sự cố vì trong trường hợp thực tế, siêu lớp của tôi là lớp Objective-C có thuộc strongtính và tôi đã gặp lỗi khi ghi đè lên nó - nhưng có vẻ như tôi đã bỏ lỡ rằng nó chuyển thành "tùy chọn không được bao bọc" ( chassis!) Swift, vì vậy override var chassis : Chassis!sửa nó.
James

4
Điều này không còn hoạt động với Swift 1.1 trong Xcode 6.1. Tạo ra lỗi: "Không thể ghi đè khung gầm 'cho phép' thuộc tính 'với bộ chuyển đổi' var '". Bất kỳ ý tưởng cho giải pháp tốt hơn?
Darrarski

1
Cũng không còn hoạt động trong Swift 1.2 trong Xcode 6.3 beta. Không thể ghi đè thuộc tính có thể thay đổi 'A' loại 'Loại 1' với loại biến đổi 'Loại 2'
bubuxu

10
Điều này thực sự khập khiễng và thất bại của Swift, imo. Vì RacingCh Khung Khung gầm, trình biên dịch sẽ không có vấn đề gì cho phép bạn tinh chỉnh lớp thuộc tính trong một lớp con. Rất nhiều ngôn ngữ cho phép điều này, và không hỗ trợ nó dẫn đến cách giải quyết xấu xí như thế này. Không xúc phạm. : /
devios1

1
Các tùy chọn cung cấp một phương tiện gợi ý rằng một biến có thể không xuất hiện bởi vì nó được cung cấp cho một hàm không còn trả về giá trị mong đợi. Những trường hợp như vậy có thể xảy ra nếu chức năng bị ghi đè. Hãy xem câu trả lời của tôi để biết chi tiết.

10

Cái này có vẻ hiệu quả

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()

1
Điều này hoạt động như thuộc tính khung được định nghĩa như lettrong Carlớp làm cho nó không thể thay đổi. Chúng tôi chỉ có thể thay đổi chassistài sản trên RaceCarlớp.
David Arve

4
Điều này không hoạt động cho Swift 2.0. Nó đưa ra "lỗi: không thể ghi đè khung 'bất động sản' bất động sản 'với bộ chuyển đổi' var '"
mohamede1945

Điều này cũng không hoạt động trong 4.0. thực thi layground không thành công: error: MyPlaygrounds.playground: 14: 15: error: không thể ghi đè khung 'let' property 'bất biến' với getter của 'var' ghi đè lên khung var: RacingChrame {^ MyPlaygrounds.playground: 11: 6: : cố gắng ghi đè tài sản ở đây, hãy để khung gầm = Khung gầm () ^
karim

7

Thử cái này:

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

Sau đó:

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

Chi tiết trong http://www.mylonly.com/14957025459875.html


Ah gọn gàng và sạch sẽ
Inder Kumar Rathore

3

Solution Dash được cung cấp hoạt động tốt ngoại trừ siêu hạng phải được khai báo bằng từ khóa let thay vì var. Đây là một giải pháp có thể nhưng KHÔNG ĐƯỢC ĐỀ XUẤT!

Giải pháp bên dưới sẽ biên dịch với Xcode 6.2, SWIFT 1.1 (nếu tất cả các lớp nằm trong các tệp swift khác nhau) nhưng nên tránh vì NÓ CÓ THỂ ĐƯA RA CÁC VÒNG BI KHÔNG GIỚI HẠN (BAO GỒM MỘT CRASH, đặc biệt là khi sử dụng các loại không tùy chọn). LƯU Ý: ĐIỀU NÀY KHÔNG LÀM VIỆC VỚI 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
        }
    }
}

1
Nó không hoạt động cho Swift 2.0. Nó cho "không thể ghi đè lên 'khung gầm' thuộc tính có thể thay đổi thuộc loại 'Khung gầm?' với loại covariant 'RacingCh Khung?' "
mohamede1945

2

Về mặt lý thuyết, bạn được phép làm theo cách này ...

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

Điều này hoạt động hoàn hảo trong một sân chơi Xcode.

Nhưng , nếu bạn thử điều này trong một dự án thực, một lỗi trình biên dịch sẽ cho bạn biết:

Tuyên bố 'view' không thể ghi đè nhiều hơn một khai báo siêu lớp

Cho đến giờ tôi chỉ kiểm tra Xcode 6.0 GM.

Thật không may, bạn sẽ phải đợi cho đến khi Apple sửa lỗi này.

Tôi cũng đã gửi báo cáo lỗi. 18518795


Đây là một giải pháp thú vị và nó hoạt động trong 6.1.
Chris Conover

1

Tôi đã thấy rất nhiều lý do tại sao việc thiết kế API bằng các biến thay vì các hàm là vấn đề và đối với tôi, việc sử dụng các thuộc tính được tính toán có cảm giác như một cách giải quyết. Có những lý do chính đáng để giữ các biến thể hiện của bạn được gói gọn. Ở đây tôi đã tạo ra một giao thức Ô tô mà Xe tuân thủ. Giao thức này có một phương thức truy cập trả về một đối tượng Khung. Vì Xe tuân theo nó, lớp con RaceCar có thể ghi đè lên nó và trả về một lớp con Khung gầm khác. Điều này cho phép lớp Xe hơi lập trình thành giao diện (Ô tô) và lớp RaceCar biết về RacingChrame có thể truy cập trực tiếp vào biến _racingChrame.

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

Một ví dụ khác về lý do tại sao thiết kế API sử dụng các biến bị hỏng là khi bạn có các biến trong giao thức. Nếu bạn muốn tách tất cả các chức năng giao thức thành một phần mở rộng bạn có thể, ngoại trừ các thuộc tính được lưu trữ không thể được đặt trong các phần mở rộng và phải được xác định trong lớp (để có được điều này để biên dịch, bạn phải bỏ mã trong Lớp AdaptableViewControll và loại bỏ biến chế độ khỏi phần mở rộng):

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

Đoạn mã trên sẽ có lỗi trình biên dịch này: "Phần mở rộng có thể không có thuộc tính được lưu trữ". Dưới đây là cách bạn có thể viết lại ví dụ trên để mọi thứ trong giao thức có thể được tách ra trong phần mở rộng bằng cách sử dụng các hàm thay thế:

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

class AdaptableViewController: UIViewController {
}

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

1

Bạn có thể đạt được nó bằng cách sử dụng thuốc generic:

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

Với phương pháp này, bạn sẽ có mã an toàn 100% mà không cần ép buộc.

Nhưng đây là một loại hack, và nếu bạn sẽ cần xác định lại một số thuộc tính hơn các khai báo lớp của bạn sẽ trông giống như một mớ hỗn độn. Vì vậy, hãy cẩn thận với phương pháp này.


1

Tùy thuộc vào cách bạn dự định sử dụng thuộc tính, cách đơn giản nhất để làm điều này là sử dụng một loại tùy chọn cho lớp con của bạn và ghi đè didSet {}phương thức cho siêu:

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

Rõ ràng bạn cần dành một chút thời gian để kiểm tra để đảm bảo các lớp có thể được khởi tạo theo cách này, nhưng bằng cách đặt các thuộc tính thành tùy chọn, bạn tự bảo vệ mình trước các tình huống mà việc truyền không còn hoạt động.


0

Bạn có thể chỉ cần tạo một biến khác của RacingCh khung.

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

class RaceCar: Car {
let raceChassis: RacingChassis
init(){
        raceChassis = RacingChassis()
}}

Điều này hoạt động, nhưng tôi nghĩ rằng câu trả lời của Dash sẽ tiến thêm một bước bằng cách ghi đè chassisvà cho phép chúng tôi lấy / đặt RacingChassiscá thể của chúng tôi với thuộc chassistính.
James

0

Thử cái này:

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

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

0

Sau đây cho phép một đối tượng được sử dụng trong các lớp cơ sở và dẫn xuất. Trong lớp dẫn xuất, sử dụng thuộc tính đối tượng dẫn xuất.

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

0

Một biến thể nhỏ của các câu trả lời khác, nhưng đơn giản và an toàn hơn với một số lợi ích tốt đẹp.

class Chassis {}
class RacingChassis : Chassis {}

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

Các lợi ích bao gồm không có giới hạn về khung gầm phải là gì (var, let, tùy chọn, v.v.) và thật dễ dàng để phân lớp RaceCar. Các lớp con của RaceCar sau đó có thể có giá trị tính toán riêng cho khung gầm (hoặc racingCh Khung).


-2

chỉ cần đặt thuộc tính hình ảnh mới với quy ước đặt tên khác nhau như imgview vì imageView đã là thuộc tính riêng của nó và chúng tôi không thể gán 2 thuộc tính mạnh.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.