Fungsi abstrak dalam Bahasa Swift


127

Saya ingin membuat fungsi abstrak dalam bahasa cepat. Apa itu mungkin?

class BaseClass {
    func abstractFunction() {
        // How do I force this function to be overridden?
    }
}

class SubClass : BaseClass {
    override func abstractFunction() {
        // Override
    }
}

Ini cukup dekat dengan pertanyaan Anda yang lain tetapi jawabannya di sini tampaknya sedikit lebih baik.
David Berry

Pertanyaannya serupa tetapi solusinya jauh berbeda karena kelas abstrak memiliki kasus penggunaan yang berbeda dari fungsi abstrak.
kev

Yap, mengapa saya tidak memilih untuk menutup juga, tetapi jawaban di sana tidak akan berguna di luar "Anda tidak bisa" Di sini Anda mendapat jawaban terbaik yang tersedia :)
David Berry

Jawaban:


198

Tidak ada konsep abstrak di Swift (seperti Objective-C) tetapi Anda dapat melakukan ini:

class BaseClass {
    func abstractFunction() {
        preconditionFailure("This method must be overridden") 
    } 
}

class SubClass : BaseClass {
     override func abstractFunction() {
         // Override
     } 
}

13
Atau Anda bisa menggunakan assert(false, "This method must be overriden by the subclass").
Erik

20
ataufatalError("This method must be overridden")
nathan

5
pernyataan akan membutuhkan pernyataan kembali ketika suatu metode memiliki tipe pengembalian. Saya pikir fatalError () lebih baik untuk alasan yang satu ini
André Fratelli

6
Juga ada preconditionFailure("Bla bla bla")di Swift yang akan mengeksekusi dalam rilis build dan juga menghilangkan kebutuhan akan pernyataan return. EDIT: Baru tahu bahwa metode ini pada dasarnya sama dengan fatalError()tetapi ini cara yang lebih tepat (Dokumentasi yang lebih baik, diperkenalkan di Swift bersama dengan precondition(), assert()dan assertionFailure(), baca di sini )
Kametrixom

2
bagaimana jika fungsi tersebut memiliki tipe pengembalian?
LoveMeow

36

Yang Anda inginkan bukan kelas dasar, tetapi sebuah protokol.

protocol MyProtocol {
    func abstractFunction()
}

class MyClass : MyProtocol {
    func abstractFunction() {
    }
}

Jika Anda tidak menyediakan Fungsi abstrak di kelas Anda itu adalah kesalahan.

Jika Anda masih membutuhkan baseclass untuk perilaku lain, Anda dapat melakukan ini:

class MyClass : BaseClass, MyProtocol {
    func abstractFunction() {
    }
}

23
Ini tidak berhasil. Jika BaseClass ingin memanggil fungsi-fungsi kosong itu, maka itu juga harus mengimplementasikan protokol, dan kemudian mengimplementasikan fungsinya. Subclass juga masih tidak dipaksa untuk mengimplementasikan fungsi-fungsi pada waktu kompilasi, dan Anda tidak dipaksa untuk mengimplementasikan protokol juga.
bandejapaisa

5
Itu maksud saya .... BaseClass tidak ada hubungannya dengan protokol, dan untuk bertindak seperti kelas abstrak itu harus ada hubungannya dengan itu. Untuk memperjelas apa yang saya tulis "Jika BaseClass ingin memanggil fungsi-fungsi kosong itu, maka itu juga harus mengimplementasikan protokol yang akan memaksa Anda untuk mengimplementasikan fungsi - yang kemudian menyebabkan Subclass tidak dipaksa untuk mengimplementasikan fungsi-fungsi pada waktu kompilasi, karena BaseClass sudah melakukannya ".
bandejapaisa

4
Itu benar-benar tergantung pada bagaimana Anda mendefinisikan "abstrak". Inti dari fungsi abstrak adalah Anda dapat meletakkannya di kelas yang dapat melakukan hal-hal kelas normal. Sebagai contoh, alasan saya menginginkan ini adalah karena saya perlu mendefinisikan anggota statis yang sepertinya tidak dapat Anda lakukan dengan protokol. Jadi sulit bagi saya untuk melihat bahwa ini memenuhi titik berguna dari fungsi abstrak.
Puppy

3
Saya pikir keprihatinan dengan komentar yang terangkat di atas adalah bahwa ini tidak sesuai dengan prinsip Pergantian Liskov yang menyatakan "objek dalam suatu program harus diganti dengan contoh subtipe mereka tanpa mengubah kebenaran program itu." Diasumsikan ini adalah tipe abstrak. Ini sangat penting dalam kasus pola Metode Pabrik di mana kelas dasar bertanggung jawab untuk membuat dan menyimpan properti yang berasal dari sub kelas.
David James

2
Kelas dasar abstrak dan protokol bukanlah hal yang sama.
Shoerob

29

Saya port cukup kode dari platform yang mendukung kelas dasar abstrak ke Swift dan menjalankan banyak ini. Jika yang Anda inginkan adalah fungsionalitas dari kelas dasar abstrak, maka itu berarti bahwa kelas ini berfungsi baik sebagai implementasi dari fungsi kelas berbasis bersama (jika tidak, itu hanya akan menjadi antarmuka / protokol) DAN itu mendefinisikan metode yang harus diimplementasikan oleh kelas turunan.

Untuk melakukannya di Swift, Anda memerlukan protokol dan kelas dasar.

protocol Thing
{
    func sharedFunction()
    func abstractFunction()
}

class BaseThing
{
    func sharedFunction()
    {
        println("All classes share this implementation")
    }
}

Perhatikan bahwa kelas dasar mengimplementasikan metode bersama, tetapi tidak mengimplementasikan protokol (karena tidak menerapkan semua metode).

Kemudian di kelas turunan:

class DerivedThing : BaseThing, Thing 
{
    func abstractFunction() 
    {
        println("Derived classes implement this");
    }
}

Kelas turunan mewarisi sharedFunction dari kelas dasar, membantunya memenuhi bagian protokol itu, dan protokol tersebut masih membutuhkan kelas turunan untuk mengimplementasikan fungsi abstrak.

Satu-satunya downside nyata untuk metode ini adalah bahwa karena kelas dasar tidak mengimplementasikan protokol, jika Anda memiliki metode kelas dasar yang membutuhkan akses ke properti / metode protokol Anda harus menimpa bahwa dalam kelas turunan, dan dari sana memanggil kelas dasar (melalui super) yang lewat selfsehingga kelas dasar memiliki instance protokol yang dapat digunakan untuk melakukan tugasnya.

Sebagai contoh, katakanlah bahwa sharedFunction diperlukan untuk memanggil abstractFunction. Protokol akan tetap sama, dan kelas sekarang akan terlihat seperti:

class BaseThing
{
    func sharedFunction(thing: Thing)
    {
        println("All classes share this implementation")
        thing.abstractFunction()
    }
}

class DerivedThing : BaseThing, Thing 
{
    func sharedFunction()
    {
        super.sharedFunction(self)
    }

    func abstractFunction() 
    {
        println("Derived classes implement this");
    }
}

Sekarang sharedFunction dari kelas turunan memuaskan bagian protokol itu, tetapi kelas turunan masih dapat berbagi logika kelas dasar dengan cara yang cukup mudah.


4
Pemahaman yang bagus dan kedalaman yang baik pada "kelas dasar tidak mengimplementasikan protokol" ... yang merupakan jenis dari kelas abstrak. Saya bingung bahwa fitur OO dasar ini hilang, tetapi sekali lagi, saya dibesarkan di Jawa.
Dan Rosenstark

2
Namun implementasinya tidak memungkinkan untuk mengimplementasikan metode Fungsi Template dengan mulus di mana metode template memanggil sejumlah besar metode abstrak yang diterapkan dalam subkelas. Dalam hal ini Anda harus menulis keduanya sebagai metode normal di kelas super, sebagai metode dalam protokol dan lagi sebagai implementasi di subkelas. Dalam praktiknya Anda harus menulis tiga kali hal yang sama dan hanya mengandalkan cek override untuk memastikan tidak membuat kesalahan ejaan yang berbahaya! Saya sangat berharap sekarang bahwa Swift terbuka untuk pengembang, fungsi abstrak lengkap akan diperkenalkan.
Fabrizio Bartolomucci

1
Begitu banyak waktu, dan kode dapat disimpan dengan memasukkan kata kunci "dilindungi".
Shoerob

23

Yang ini tampaknya menjadi cara "resmi" bagaimana Apple menangani metode abstrak di UIKit. Lihatlah UITableViewControllerdan cara kerjanya UITableViewDelegate. Salah satu hal pertama yang Anda lakukan, adalah untuk menambahkan baris: delegate = self. Nah, itulah triknya.

1. Masukkan metode abstrak dalam sebuah protokol
protocol AbstractMethodsForClassX {
    func abstractMethod() -> String
}
2. Tulis kelas dasar Anda
/// It takes an implementation of the protocol as property (same like the delegate in UITableViewController does)
/// And does not implement the protocol as it does not implement the abstract methods. It has the abstract methods available in the `delegate`
class BaseClassX {
    var delegate: AbstractMethodsForClassX!

    func doSomethingWithAbstractMethod() -> String {
        return delegate.abstractMethod() + " - And I believe it"
    }
}
3. Tuliskan Subclass (es).
/// First and only additional thing you have to do, you must set the "delegate" property
class ClassX: BaseClassX, AbstractMethodsForClassX {
    override init() {
        super.init()
        delegate = self
    }

    func abstractMethod() -> String {return "Yes, this works!"}
}
Inilah, bagaimana Anda menggunakan semua itu
let x = ClassX()
x.doSomethingWithAbstractMethod()

Periksa dengan Playground untuk melihat hasilnya.

Beberapa komentar

  • Pertama, banyak jawaban sudah diberikan. Saya harap seseorang menemukan jalan sampai ke yang ini.
  • Pertanyaan sebenarnya adalah, untuk menemukan pola yang mengimplementasikan:
    • Kelas memanggil metode, yang harus diimplementasikan dalam salah satu subkelas turunannya (ditimpa)
    • Dalam kasus terbaik, jika metode ini tidak ditimpa dalam subkelas, dapatkan kesalahan selama waktu kompilasi
  • Hal tentang metode abstrak adalah, bahwa mereka adalah campuran dari definisi antarmuka dan bagian dari implementasi aktual di kelas dasar. Keduanya sekaligus. Karena cepat sangat baru dan sangat bersih didefinisikan, ia tidak memiliki kenyamanan tetapi konsep "kotor" (belum).
  • Bagi saya (orang Jawa tua yang malang), masalah ini berkembang dari waktu ke waktu. Saya telah membaca melalui semua jawaban di posting ini dan kali ini saya pikir saya menemukan sebuah pola, yang terlihat cocok - setidaknya bagi saya.
  • Pembaruan : Sepertinya para pelaksana UIKit di Apple menggunakan pola yang sama. UITableViewControllermengimplementasikan UITableViewDelegatetetapi masih perlu didaftarkan sebagai delegasi dengan menetapkan delegateproperti secara eksplisit .
  • Ini semua diuji di Playground of Xcode 7.3.1

Mungkin tidak sempurna, karena saya punya masalah menyembunyikan antarmuka kelas dari kelas lain, tapi ini cukup yang saya butuhkan untuk menerapkan Metode Pabrik klasik di Swift.
Tomasz Nazarenko

Hmmm. Saya lebih menyukai jawaban ini, tapi saya juga tidak yakin itu cocok. Yaitu, saya memiliki ViewController yang dapat menampilkan informasi untuk beberapa jenis objek yang berbeda, tetapi tujuan menampilkan informasi itu semua sama, dengan semua metode yang sama, tetapi menarik dan menyusun informasi secara berbeda berdasarkan objek. . Jadi, saya memiliki kelas delegasi induk untuk VC ini, dan subkelas dari delegasi itu untuk setiap jenis objek. Saya instantiate delegasi berdasarkan objek yang dilewatkan. Dalam satu contoh, setiap objek memiliki beberapa komentar yang relevan disimpan.
Jake T.

Jadi saya punya getCommentsmetode pada delegasi induk. Ada beberapa jenis komentar, dan saya ingin yang relevan untuk setiap objek. Jadi, saya ingin subclass tidak dikompilasi ketika saya lakukan delegate.getComments()jika saya tidak menimpa metode itu. VC hanya tahu ia memiliki ParentDelegateobjek yang disebut delegate, atau BaseClassXdalam contoh Anda, tetapi BaseClassXtidak memiliki metode abstrak. Saya membutuhkan VC untuk secara khusus tahu bahwa itu menggunakan SubclassedDelegate.
Jake T.

Saya harap saya mengerti Anda dengan benar. Jika tidak, apakah Anda keberatan mengajukan pertanyaan baru di mana Anda dapat menambahkan beberapa kode? Saya pikir Anda hanya perlu memiliki pernyataan if di checking doSomethingWithAbstractMethod 'memeriksa, apakah delegasi diatur atau tidak.
jboi

Perlu disebutkan bahwa menugaskan diri kepada delegasi menciptakan siklus tetap. Mungkin lebih baik untuk menyatakannya sebagai lemah (yang biasanya merupakan ide bagus untuk delegasi)
Enricoza

7

Salah satu cara untuk melakukan ini adalah dengan menggunakan penutupan opsional yang ditentukan dalam kelas dasar, dan anak-anak dapat memilih untuk mengimplementasikannya atau tidak.

class BaseClass {
    var abstractClosure?:(()->())?
    func someFunc()
    {
        if let abstractClosure=abstractClosure
        {
            abstractClosure()
        }
    } 
}

class SubClass : BaseClass {
    init()
    {
        super.init()
        abstractClosure={ ..... }
    }
}

Apa yang saya sukai dari pendekatan ini adalah saya tidak harus ingat untuk mengimplementasikan protokol di kelas pewarisan. Saya memiliki kelas dasar untuk ViewControllers saya, dan ini adalah bagaimana saya menegakkan fungsionalitas opsional, opsional ViewController (misalnya aplikasi menjadi aktif, dll.) Yang mungkin dipanggil oleh fungsionalitas kelas dasar.
ProgrammierTier

6

Yah, saya tahu bahwa saya terlambat ke permainan dan bahwa saya mungkin mengambil keuntungan dari perubahan yang telah terjadi. Maaf soal itu.

Bagaimanapun, saya ingin berkontribusi jawaban saya, karena saya suka melakukan pengujian dan solusinya fatalError()adalah, AFAIK, tidak dapat diuji dan yang dengan pengecualian jauh lebih sulit untuk diuji.

Saya akan menyarankan untuk menggunakan lebih swifty pendekatan. Tujuan Anda adalah untuk mendefinisikan abstraksi yang memiliki beberapa detail umum, tetapi itu tidak sepenuhnya didefinisikan, yaitu metode abstrak. Gunakan protokol yang mendefinisikan semua metode yang diharapkan dalam abstraksi, baik yang sudah didefinisikan, maupun yang tidak. Kemudian buat ekstensi protokol yang mengimplementasikan metode yang didefinisikan dalam kasus Anda. Akhirnya, setiap kelas turunan harus mengimplementasikan protokol, yang berarti semua metode, tetapi yang merupakan bagian dari ekstensi protokol sudah memiliki implementasinya.

Memperluas contoh Anda dengan satu fungsi konkret:

protocol BaseAbstraction {
    func abstractFunction() {
        // How do I force this function to be overridden?
    }
}

extension BaseAbstraction {
    func definedFunction() {
        print("Hello")
}

class SubClass : BaseAbstraction {
    func abstractFunction() {
        // No need to "Override". Just implement.
    }
}

Perhatikan bahwa dengan melakukan ini, kompiler adalah lagi teman Anda. Jika metode ini tidak "diganti", Anda akan mendapatkan kesalahan pada waktu kompilasi, alih-alih yang Anda akan dapatkan fatalError()atau pengecualian yang akan terjadi pada waktu berjalan.


1
Imo jawaban terbaik.
Apfelsaft

1
Jawaban yang bagus, tetapi abstraksi dasar Anda tidak mampu menyimpan properti,
joehinkle11

5

Saya mengerti apa yang Anda lakukan sekarang, saya pikir Anda akan lebih baik menggunakan protokol

protocol BaseProtocol {
    func abstractFunction()
}

Kemudian, Anda cukup mematuhi protokol:

class SubClass : BaseProtocol {

    func abstractFunction() {
        // Override
        println("Override")
    }
}

Jika kelas Anda juga merupakan subkelas, protokol ikuti Superclass:

class SubClass: SuperClass, ProtocolOne, ProtocolTwo {}

3

Menggunakan assertkata kunci untuk menegakkan metode abstrak:

class Abstract
{
    func doWork()
    {
        assert(false, "This method must be overriden by the subclass")
    }
}

class Concrete : Abstract
{
    override func doWork()
    {
        println("Did some work!")
    }
}

let abstract = Abstract()
let concrete = Concrete()

abstract.doWork()    // fails
concrete.doWork()    // OK

Namun, seperti yang disebutkan Steve Waddicor, Anda mungkin menginginkannya protocol.


Manfaat dari metode abstrak adalah bahwa pemeriksaan dilakukan pada waktu kompilasi.
Fabrizio Bartolomucci

jangan gunakan penegasan, karena pada pengarsipan, Anda akan mendapatkan kesalahan 'Hilang kembali dalam fungsi yang diharapkan untuk kembali'. Gunakan fatalError ("Metode ini harus diganti oleh subclass")
Zaporozhchenko Oleksandr

2

Saya mengerti pertanyaan itu dan sedang mencari solusi yang sama. Protokol tidak sama dengan metode abstrak.

Dalam protokol Anda perlu menentukan bahwa kelas Anda sesuai dengan protokol tersebut, metode abstrak berarti bahwa Anda harus mengganti metode tersebut.

Dengan kata lain, protokol adalah jenis opsional, Anda perlu menentukan kelas dasar dan protokol, jika Anda tidak menentukan protokol, maka Anda tidak perlu menimpa metode tersebut.

Metode abstrak berarti Anda menginginkan kelas dasar tetapi perlu menerapkan metode Anda sendiri atau dua, yang tidak sama.

Saya membutuhkan perilaku yang sama, itu sebabnya saya mencari solusi. Saya kira Swift tidak memiliki fitur tersebut.


1

Ada alternatif lain untuk masalah ini, meskipun masih ada kelemahan jika dibandingkan dengan proposal @ jaumard; itu membutuhkan pernyataan pengembalian. Meskipun saya kehilangan titik memerlukannya, karena itu terdiri dari langsung melemparkan pengecualian:

class AbstractMethodException : NSException {

    init() {
        super.init(
            name: "Called an abstract method",
            reason: "All abstract methods must be overriden by subclasses",
            userInfo: nil
        );
    }
}

Lalu:

class BaseClass {
    func abstractFunction() {
        AbstractMethodException.raise();
    }
}

Apa pun yang terjadi setelah itu tidak dapat dijangkau, jadi saya tidak melihat mengapa memaksakan pengembalian.


Apakah yang Anda maksud AbstractMethodException().raise()?
Joshcodes

Err ... Mungkin. Saya tidak bisa menguji sekarang, tetapi jika itu bekerja seperti itu, daripada ya
André Fratelli

1

Saya tidak tahu apakah ini akan berguna, tetapi saya memiliki masalah serupa dengan metode abstrak saat mencoba membangun game SpritKit. Apa yang saya inginkan adalah kelas Animal abstrak yang memiliki metode seperti move (), run () dll, tetapi nama sprite (dan fungsi lainnya) harus disediakan oleh anak-anak kelas. Jadi saya akhirnya melakukan sesuatu seperti ini (diuji untuk Swift 2):

import SpriteKit

// --- Functions that must be implemented by child of Animal
public protocol InheritedAnimal
{
    func walkSpriteNames() -> [String]
    func runSpriteNames() -> [String]
}


// --- Abstract animal
public class Animal: SKNode
{
    private let inheritedAnimal: InheritedAnimal

    public init(inheritedAnimal: InheritedAnimal)
    {
        self.inheritedAnimal = inheritedAnimal
        super.init()
    }

    public required init?(coder aDecoder: NSCoder)
    {
        fatalError("NSCoding not supported")
    }

    public func walk()
    {
        let sprites = inheritedAnimal.walkSpriteNames()
        // create animation with walking sprites...
    }

    public func run()
    {
        let sprites = inheritedAnimal.runSpriteNames()
        // create animation with running sprites
    }
}


// --- Sheep
public class SheepAnimal: Animal
{
    public required init?(coder aDecoder: NSCoder)
    {
        fatalError("NSCoding not supported")
    }

    public required init()
    {
        super.init(inheritedAnimal: InheritedAnimalImpl())
    }

    private class InheritedAnimalImpl: InheritedAnimal
    {
        init() {}

        func walkSpriteNames() -> [String]
        {
            return ["sheep_step_01", "sheep_step_02", "sheep_step_03", "sheep_step_04"]
        }

        func runSpriteNames() -> [String]
        {
            return ["sheep_run_01", "sheep_run_02"]
        }
    }
}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.