Ghi đè một thuộc tính được lưu trữ trong Swift


126

Tôi nhận thấy rằng trình biên dịch sẽ không cho phép tôi ghi đè một thuộc tính được lưu trữ bằng một giá trị được lưu trữ khác (có vẻ kỳ lạ):

class Jedi {
    var lightSaberColor = "Blue"
}


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

Tuy nhiên, tôi được phép làm điều này với thuộc tính tính toán:

class Jedi {
    let lightSaberColor = "Blue"
}


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

}

Tại sao tôi không được phép cho nó một giá trị khác?

Tại sao việc ghi đè với một thuộc tính được lưu trữ là một điều ghê tởm và làm điều đó với một kosher được tính toán? Họ đang nghĩ gì?


Câu trả lời:


82

Tại sao tôi không được phép chỉ cho nó một giá trị khác?

Bạn chắc chắn được phép cung cấp cho một tài sản được thừa kế một giá trị khác. Bạn có thể làm điều đó nếu bạn khởi tạo thuộc tính trong một hàm tạo nhận giá trị ban đầu đó và chuyển một giá trị khác từ lớp dẫn xuất:

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)

Ghi đè một thuộc tính không giống như cấp cho nó một giá trị mới - nó giống như cấp cho một lớp một thuộc tính khác. Trên thực tế, đó là những gì xảy ra khi bạn ghi đè thuộc tính được tính toán: mã tính toán thuộc tính trong lớp cơ sở được thay thế bằng mã tính toán ghi đè cho thuộc tính đó trong lớp dẫn xuất.

[Có thể] ghi đè thuộc tính được lưu trữ thực tế, tức là lightSaberColorcó một số hành vi khác không?

Ngoài các quan sát viên, các thuộc tính được lưu trữ không có hành vi, vì vậy thực sự không có gì ở đó để ghi đè. Có thể cung cấp cho tài sản một giá trị khác thông qua cơ chế được mô tả ở trên. Điều này thực hiện chính xác những gì ví dụ trong câu hỏi đang cố gắng đạt được, với một cú pháp khác.


2
@MaxMacLeod Ngoài trình quan sát, các thuộc tính được lưu trữ không có hành vi, vì vậy thực sự không có gì ở đó để ghi đè. Anh ta muốn cung cấp cho một thuộc tính được lưu trữ một giá trị khác trong lớp con, nhưng anh ta không chắc chắn về cơ chế để đạt được nó. Câu trả lời giải thích cách nó có thể được thực hiện trong Swift. Tôi xin lỗi vì đã trả lời muộn, có vẻ như nhận xét của bạn gây ra đủ sự nhầm lẫn để thu hút lượt phản đối, vì vậy tôi quyết định giải thích những gì đang xảy ra.
dasblinkenlight

55

Đối với tôi, ví dụ của bạn không hoạt động trong Swift 3.0.1.

Tôi đã tham gia vào sân chơi mã này:

class Jedi {
    let lightsaberColor = "Blue"
}

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

Ném lỗi tại thời điểm biên dịch trong Xcode:

không thể ghi đè thuộc tính 'let' bất biến 'lightsaberColor' bằng getter là 'var'

Không, bạn không thể thay đổi loại tài sản được lưu trữ. Nguyên tắc thay thế Liskov buộc bạn phải cho phép một lớp con được sử dụng ở nơi mà lớp cha đó được mong muốn.

Nhưng nếu bạn thay đổi nó thành varvà do đó thêm setthuộc tính trong thuộc tính đã tính toán, bạn có thể ghi đè thuộc tính được lưu trữ bằng thuộc tính được tính toán cùng loại.

class Jedi {
    var lightsaberColor = "Blue"
}


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

Điều này có thể thực hiện được vì có thể hợp lý khi chuyển từ thuộc tính được lưu trữ sang thuộc tính được tính toán.

Nhưng ghi đè một thuộc tính được lưu trữ varbằng một thuộc tính được lưu trữ varkhông có ý nghĩa, vì bạn chỉ có thể thay đổi giá trị của thuộc tính.

Tuy nhiên, bạn có thể không ghi đè một thuộc tính được lưu trữ bằng một thuộc tính được lưu trữ.


Tôi sẽ không nói Sith là Jedi :-P. Do đó rõ ràng là điều này không thể hoạt động.


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

13
Mặc dù đoạn mã này có thể giải quyết câu hỏi, bao gồm một lời giải thích thực sự giúp cải thiện chất lượng bài đăng của bạn. Hãy nhớ rằng bạn đang trả lời câu hỏi cho người đọc trong tương lai và những người đó có thể không biết lý do cho đề xuất mã của bạn.
DimaSan

15

Bạn có thể muốn gán một giá trị khác cho thuộc tính:

class Jedi {
    var lightSaberColor = "Blue"
}


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

9
cũng áp dụng theo bình luận trên
Max MacLeod

10

Đối với Swift 4, từ tài liệu của Apple :

Bạn có thể ghi đè thuộc tính loại hoặc phiên bản được kế thừa để cung cấp bộ nhận và bộ cài đặt tùy chỉnh của riêng bạn cho thuộc tính đó hoặc thêm người quan sát thuộc tính để cho phép thuộc tính ghi đè quan sát khi giá trị thuộc tính cơ bản thay đổi.


7

Trong Swift, điều này rất tiếc là không thể làm được. Giải pháp thay thế tốt nhất là:

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


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

3

Tôi đã gặp vấn đề tương tự khi đặt một hằng số cho bộ điều khiển chế độ xem.

Vì tôi đang sử dụng trình tạo giao diện để quản lý chế độ xem, nên tôi không thể sử dụng init(), vì vậy cách giải quyết của tôi tương tự như các câu trả lời khác, ngoại trừ tôi đã sử dụng biến được tính chỉ đọc trên cả lớp cơ sở và lớp kế thừa.

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

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

3

Nếu bạn cố gắng làm điều đó trong Swift 5, bạn sẽ được chào đón bởi

Không thể ghi đè thuộc tính 'let' bất biến 'lightSaberColor' bằng getter là 'var'

Đặt cược tốt nhất của bạn là khai báo nó như một tài sản được tính toán.

Điều này hoạt động vì chúng tôi chỉ ghi đè get {}hàm

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

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

2

Swift không cho phép bạn ghi đè một biến stored propertyThay vì điều này, bạn có thể sử dụngcomputed 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())
}

Rõ ràng hơn khi so sánh với Java, nơi một trường lớp không thể được ghi đè và không hỗ trợ tính đa hình vì được xác định trong thời gian biên dịch (chạy hiệu quả). Nó được gọi là ẩn biến [Giới thiệu] Không nên sử dụng kỹ thuật này vì nó khó đọc / hỗ trợ

[Thuộc tính Swift]


1

Bạn cũng có thể sử dụng một hàm để ghi đè. Nó không phải là câu trả lời trực tiếp, nhưng có thể làm phong phú thêm chủ đề này)

Hạng A

override func viewDidLoad() {
    super.viewDidLoad()

    if shouldDoSmth() {
       // do
    }
}

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

Loại B: A

public func shouldDoSmth() -> Bool {
    return false
}
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.