การแทนที่คุณสมบัติที่เก็บไว้ใน Swift


126

ฉันสังเกตเห็นว่าคอมไพเลอร์ไม่ยอมให้ฉันแทนที่คุณสมบัติที่เก็บไว้ด้วยค่าอื่นที่เก็บไว้ (ซึ่งดูแปลก):

class Jedi {
    var lightSaberColor = "Blue"
}


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

อย่างไรก็ตามฉันได้รับอนุญาตให้ทำสิ่งนี้กับคุณสมบัติที่คำนวณได้:

class Jedi {
    let lightSaberColor = "Blue"
}


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

}

เหตุใดฉันจึงไม่ได้รับอนุญาตให้ให้ค่าอื่น

เหตุใดการลบล้างทรัพย์สินที่เก็บไว้จึงเป็นสิ่งที่น่ารังเกียจและทำด้วยโคเชอร์ที่คำนวณได้ พวกเขาคิดอะไรอยู่?


คำตอบ:


82

เหตุใดฉันจึงไม่ได้รับอนุญาตให้เพียงแค่ให้ค่าอื่น

คุณได้รับอนุญาตให้มอบทรัพย์สินที่สืบทอดมาในมูลค่าที่แตกต่างออกไป คุณสามารถทำได้หากคุณเตรียมใช้งานคุณสมบัติในตัวสร้างที่รับค่าเริ่มต้นนั้นและส่งผ่านค่าที่แตกต่างจากคลาสที่ได้รับ:

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)

การลบล้างคุณสมบัตินั้นไม่เหมือนกับการให้ค่าใหม่มันเหมือนกับการให้คุณสมบัติอื่นแก่คลาส ในความเป็นจริงนั่นคือสิ่งที่เกิดขึ้นเมื่อคุณแทนที่คุณสมบัติที่คำนวณ: รหัสที่คำนวณคุณสมบัติในคลาสพื้นฐานจะถูกแทนที่ด้วยโค้ดที่คำนวณการแทนที่สำหรับคุณสมบัตินั้นในคลาสที่ได้รับ

[เป็นไปได้ไหมที่จะลบล้างคุณสมบัติที่เก็บไว้จริงกล่าวคือlightSaberColorมีพฤติกรรมอื่น ๆ

นอกเหนือจากผู้สังเกตการณ์คุณสมบัติที่จัดเก็บจะไม่มีพฤติกรรมดังนั้นจึงไม่มีอะไรที่จะแทนที่ได้ การให้ทรัพย์สินมีมูลค่าที่แตกต่างกันเป็นไปได้โดยใช้กลไกที่อธิบายไว้ข้างต้น สิ่งนี้ทำตามตัวอย่างในคำถามที่พยายามจะบรรลุด้วยไวยากรณ์ที่แตกต่างกัน


2
@MaxMacLeod นอกเหนือจากผู้สังเกตการณ์คุณสมบัติที่เก็บไว้ไม่มีพฤติกรรมดังนั้นจึงไม่มีอะไรที่จะแทนที่ได้ เขาต้องการให้คุณสมบัติที่เก็บไว้เป็นมูลค่าที่แตกต่างกันในคลาสย่อย แต่เขาไม่แน่ใจเกี่ยวกับกลไกที่จะบรรลุมัน คำตอบอธิบายวิธีการทำได้ใน Swift ฉันขอโทษที่ตอบช้าดูเหมือนว่าความคิดเห็นของคุณทำให้เกิดความสับสนมากพอที่จะดึงดูดผู้โหวตให้ลงคะแนนดังนั้นฉันจึงตัดสินใจที่จะอธิบายสิ่งที่เกิดขึ้น
dasblinkenlight

55

สำหรับฉันตัวอย่างของคุณใช้ไม่ได้ใน Swift 3.0.1

ฉันป้อนรหัสนี้ในสนามเด็กเล่น:

class Jedi {
    let lightsaberColor = "Blue"
}

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

แสดงข้อผิดพลาดในเวลาคอมไพล์ใน Xcode:

ไม่สามารถแทนที่ 'let' property 'lightsaberColor' ที่ไม่เปลี่ยนรูปด้วย getter ของ 'var'

ไม่คุณไม่สามารถเปลี่ยนประเภทของคุณสมบัติที่จัดเก็บไว้ได้ หลักการเปลี่ยนตัว Liskov บังคับให้คุณอนุญาตให้ใช้คลาสย่อยในสถานที่ที่ต้องการซูเปอร์คลาส

แต่ถ้าคุณเปลี่ยนเป็นvarและเพิ่มคุณสมบัติsetในการคำนวณคุณสามารถแทนที่คุณสมบัติที่เก็บไว้ด้วยคุณสมบัติที่คำนวณจากประเภทเดียวกันได้

class Jedi {
    var lightsaberColor = "Blue"
}


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

สิ่งนี้เป็นไปได้เนื่องจากการเปลี่ยนจากคุณสมบัติที่เก็บไว้เป็นคุณสมบัติที่คำนวณได้นั้นสมเหตุสมผล

แต่การแทนที่varคุณสมบัติที่เก็บไว้ด้วยคุณสมบัติที่เก็บไว้varนั้นไม่สมเหตุสมผลเพราะคุณสามารถเปลี่ยนค่าของคุณสมบัติได้เท่านั้น

อย่างไรก็ตามคุณไม่สามารถแทนที่คุณสมบัติที่จัดเก็บด้วยคุณสมบัติที่เก็บไว้ได้เลย


ฉันจะไม่บอกว่าซิ ธ เป็นเจได :-P ดังนั้นจึงเป็นที่ชัดเจนว่าไม่สามารถทำงานได้


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

13
แม้ว่าข้อมูลโค้ดนี้จะช่วยแก้ปัญหาได้ แต่คำอธิบายจะช่วยปรับปรุงคุณภาพของโพสต์ของคุณได้มาก โปรดจำไว้ว่าคุณกำลังตอบคำถามสำหรับผู้อ่านในอนาคตและบุคคลเหล่านั้นอาจไม่ทราบสาเหตุของการแนะนำโค้ดของคุณ
DimaSan

15

คุณอาจต้องการกำหนดค่าอื่นให้กับคุณสมบัติ:

class Jedi {
    var lightSaberColor = "Blue"
}


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

9
เช่นเดียวกับความคิดเห็นด้านบน
Max MacLeod

10

สำหรับ Swift 4 จากเอกสารของ Apple :

คุณสามารถแทนที่อินสแตนซ์หรือคุณสมบัติประเภทที่สืบทอดมาเพื่อจัดเตรียม getter และ setter ที่คุณกำหนดเองสำหรับคุณสมบัตินั้นหรือเพิ่มผู้สังเกตการณ์คุณสมบัติเพื่อเปิดใช้งานคุณสมบัติการแทนที่เพื่อสังเกตเมื่อค่าคุณสมบัติพื้นฐานเปลี่ยนแปลง


7

ใน Swift น่าเสียดายที่ไม่สามารถทำได้ ทางเลือกที่ดีที่สุดมีดังต่อไปนี้:

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


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

3

ฉันมีปัญหาเดียวกันในการตั้งค่าคงที่สำหรับตัวควบคุมมุมมอง

เนื่องจากฉันใช้ตัวสร้างอินเทอร์เฟซเพื่อจัดการมุมมองฉันจึงไม่สามารถใช้init()วิธีแก้ปัญหาของฉันจึงคล้ายกับคำตอบอื่น ๆ ยกเว้นฉันใช้ตัวแปรที่คำนวณแบบอ่านอย่างเดียวทั้งในคลาสพื้นฐานและคลาสที่สืบทอดมา

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

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

3

หากคุณพยายามทำเช่นนั้นใน Swift 5 คุณจะได้รับการต้อนรับด้วยไฟล์

ไม่สามารถแทนที่คุณสมบัติ 'let' property 'lightSaberColor' ที่ไม่เปลี่ยนรูปด้วย getter ของ 'var'

ทางออกที่ดีที่สุดของคุณคือการประกาศว่าเป็นทรัพย์สินที่คำนวณได้

สิ่งนี้ได้ผลในขณะที่เรากำลังลบล้างget {}ฟังก์ชัน

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

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

2

Swift ไม่อนุญาตให้คุณแทนที่ตัวแปรstored propertyแทนสิ่งนี้คุณสามารถใช้ได้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())
}

มีความชัดเจนมากขึ้นเมื่อเปรียบเทียบกับ Java โดยที่ฟิลด์คลาสไม่สามารถ overrode และไม่สนับสนุน polymorphism เนื่องจากกำหนดไว้ในเวลาคอมไพล์ (ทำงานอย่างมีประสิทธิภาพ) เรียกว่าตัวแปรซ่อน[เกี่ยวกับ]ไม่แนะนำให้ใช้เทคนิคนี้เพราะอ่าน / สนับสนุนยาก

[คุณสมบัติ Swift]


1

คุณยังสามารถใช้ฟังก์ชันเพื่อลบล้าง ไม่ใช่คำตอบโดยตรง แต่สามารถเสริมสร้างหัวข้อนี้ได้)

คลาส A

override func viewDidLoad() {
    super.viewDidLoad()

    if shouldDoSmth() {
       // do
    }
}

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

คลาส B: A

public func shouldDoSmth() -> Bool {
    return false
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.