Swift - วิธีคลาสที่ต้องถูกแทนที่โดยคลาสย่อย


91

มีวิธีมาตรฐานในการสร้าง "ฟังก์ชันเสมือนจริง" ใน Swift หรือไม่เช่น. สิ่งที่ต้องถูกแทนที่โดยทุกคลาสย่อยและข้อใดไม่ทำให้เกิดข้อผิดพลาดเวลาคอมไพล์


คุณสามารถนำไปใช้ในระดับซูเปอร์คลาสและยืนยันได้ ฉันเคยเห็นสิ่งนี้ใช้ใน Obj-C, Java และ Python
David Skrundz

8
@NSArray สิ่งนี้ทำให้รันไทม์ไม่ใช่เวลาคอมไพล์ข้อผิดพลาด
JuJoDi

คำตอบนี้จะช่วยคุณได้เช่นกัน ใส่คำอธิบายลิงค์ที่นี่
Chamath Jeevan

ฟังก์ชั่นเสมือนจริงถูกใช้งานโดยprotocols (เทียบกับinterfaces ใน Java) หากคุณต้องการใช้มันเหมือนวิธีนามธรรมให้ดูที่คำถาม / คำตอบนี้: stackoverflow.com/a/39038828/2435872
jboi

คำตอบ:


153

คุณมีสองทางเลือก:

1. ใช้โปรโตคอล

กำหนด superclass เป็น Protocol แทน Class

Pro : เวลาคอมไพล์ตรวจสอบว่า "คลาสย่อย" แต่ละรายการ (ไม่ใช่คลาสย่อยจริง) ใช้วิธีการที่ต้องการหรือไม่

Con : "superclass" (โปรโตคอล) ไม่สามารถใช้วิธีการหรือคุณสมบัติได้

2. ยืนยันในเวอร์ชันขั้นสูงของวิธีการ

ตัวอย่าง:

class SuperClass {
    func someFunc() {
        fatalError("Must Override")
    }
}

class Subclass : SuperClass {
    override func someFunc() {
    }
}

Pro : สามารถใช้วิธีการและคุณสมบัติใน superclass

Con : ไม่มีการตรวจสอบเวลาคอมไพล์


3
@jewirth คุณยังไม่ได้รับการตรวจสอบเวลารวบรวมในคลาสย่อย
drewag

6
โปรโตคอลไม่สามารถใช้วิธีการได้ แต่คุณสามารถใช้วิธีการขยายแทนได้
David Moles

2
ในฐานะของสวิฟท์ 2.0 ขณะนี้มีส่วนขยายโปรโตคอลเกินไป :) แอปเปิ้ลอ้างอิง
Ephemera

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

3
กรณีที่ 2:โปรดทราบว่าหากคุณเรียกsuper.someFunc()ใช้วิธีการแทนที่คุณจะได้รับข้อผิดพลาดแม้ว่าคุณจะลบล้างไปแล้วก็ตาม คุณรู้ว่าคุณไม่ควรเรียกมัน แต่คนอื่นไม่จำเป็นต้องรู้เรื่องนั้นและทำตามแนวปฏิบัติมาตรฐาน
Jakub Truhlář

50

ต่อไปนี้อนุญาตให้สืบทอดจากคลาสและตรวจสอบเวลาคอมไพล์ของโปรโตคอล :)

protocol ViewControllerProtocol {
    func setupViews()
    func setupConstraints()
}

typealias ViewController = ViewControllerClass & ViewControllerProtocol

class ViewControllerClass : UIViewController {

    override func viewDidLoad() {
        self.setup()
    }

    func setup() {
        guard let controller = self as? ViewController else {
            return
        }

        controller.setupViews()
        controller.setupConstraints()
    }

    //.... and implement methods related to UIViewController at will

}

class SubClass : ViewController {

    //-- in case these aren't here... an error will be presented
    func setupViews() { ... }
    func setupConstraints() { ... }

}

2
ดีผู้พิมพ์เพื่อช่วยเหลือ :)
Chris Allinson

มีวิธีใดที่จะหยุดผู้ใช้ API นี้จากการรับคลาส clild จาก ViewControllerClass แทนที่จะมาจาก ViewController นี่เป็นทางออกที่ดีสำหรับฉันเพราะฉันจะได้มาจากนามแฝงประเภทของฉันในอีกไม่กี่ปีนับจากนี้และจะลืมไปว่าฟังก์ชันใดที่ต้องถูกแทนที่ในตอนนั้น
David Rector

@David Rector คุณสามารถทำให้ชั้นเรียนของคุณเป็นส่วนตัวและประเภทของคุณเป็นสาธารณะได้หรือไม่? ขออภัยส่งข้อความจากโทรศัพท์ตรวจสอบตัวเองไม่ได้
ScottyBlades

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

35

ไม่มีการรองรับคลาสนามธรรม / ฟังก์ชันเสมือน แต่คุณอาจใช้โปรโตคอลสำหรับกรณีส่วนใหญ่:

protocol SomeProtocol {
    func someMethod()
}

class SomeClass: SomeProtocol {
    func someMethod() {}
}

หาก SomeClass ไม่ใช้ someMethod คุณจะได้รับข้อผิดพลาดเวลาคอมไพล์นี้:

error: type 'SomeClass' does not conform to protocol 'SomeProtocol'

30
หมายเหตุสิ่งนี้ใช้ได้เฉพาะกับคลาสบนสุดที่ใช้โปรโตคอล คลาสย่อยใด ๆ สามารถเพิกเฉยต่อข้อกำหนดของโปรโตคอลได้
memmons

2
นอกจากนี้ไม่รองรับการใช้ generics ในโปรโตคอล = (
Dielson Sales

14

วิธีแก้ปัญหาอื่นหากคุณไม่มีเมธอด "เสมือน" มากเกินไปคือการให้คลาสย่อยส่ง "การใช้งาน" ไปยังตัวสร้างคลาสพื้นฐานเป็นอ็อบเจ็กต์ฟังก์ชัน:

class MyVirtual {

    // 'Implementation' provided by subclass
    let fooImpl: (() -> String)

    // Delegates to 'implementation' provided by subclass
    func foo() -> String {
        return fooImpl()
    }

    init(fooImpl: (() -> String)) {
        self.fooImpl = fooImpl
    }
}

class MyImpl: MyVirtual {

    // 'Implementation' for super.foo()
    func myFoo() -> String {
        return "I am foo"
    }

    init() {
        // pass the 'implementation' to the superclass
        super.init(myFoo)
    }
}

1
ไม่มีประโยชน์หากคุณมีวิธีการเสมือนจริงอีกสองสามวิธี
Bushra Shahid

@ xs2bush หากวิธีการของคุณเป็นเสมือนมากกว่าคุณอาจจะไม่ดีกว่าที่จะประกาศในโปรโตคอลและให้วิธีการที่ 'ไม่ใช่เสมือน' ผ่านวิธีการขยาย
David Moles

1
นั่นคือสิ่งที่ฉันทำ
Bushra Shahid

1

คุณสามารถใช้โปรโตคอล VS ยืนยันตามข้อเสนอแนะในคำตอบที่นี่drewagโดย อย่างไรก็ตามตัวอย่างสำหรับโปรโตคอลหายไป ฉันครอบคลุมที่นี่

มาตรการ

protocol SomeProtocol {
    func someMethod()
}

class SomeClass: SomeProtocol {
    func someMethod() {}
}

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

ข้อผิดพลาด: ประเภท 'SomeClass' ไม่สอดคล้องกับโปรโตคอล 'SomeProtocol'

หมายเหตุ:ใช้ได้เฉพาะกับคลาสบนสุดที่ใช้โปรโตคอล คลาสย่อยใด ๆ สามารถเพิกเฉยต่อข้อกำหนดของโปรโตคอลได้ - ตามความเห็นของmemmons

การยืนยัน

class SuperClass {
    func someFunc() {
        fatalError("Must Override")
    }
}

class Subclass : SuperClass {
    override func someFunc() {
    }
}

อย่างไรก็ตามการยืนยันจะใช้ได้เฉพาะในรันไทม์เท่านั้น


-2

เนื่องจากยังใหม่กับการพัฒนา iOS ฉันไม่แน่ใจว่าจะใช้งานได้เมื่อใด แต่วิธีหนึ่งที่จะทำให้ทั้งสองโลกได้รับสิ่งที่ดีที่สุดคือการใช้ส่วนขยายสำหรับโปรโตคอล:

protocol ThingsToDo {
    func doThingOne()
}

extension ThingsToDo {
    func doThingTwo() { /* Define code here */}
}

class Person: ThingsToDo {
    func doThingOne() {
        // Already defined in extension
        doThingTwo()
        // Rest of code
    }
}

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


1
ฟังก์ชันนามธรรมตรงข้ามกับการใช้งานเริ่มต้น
Hogdotmac
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.