Kelas abstrak dalam Bahasa Swift

140

Apakah ada cara untuk membuat kelas abstrak dalam Bahasa Swift, atau apakah ini keterbatasan seperti Objective-C? Saya ingin membuat kelas abstrak yang sebanding dengan apa yang didefinisikan Java sebagai kelas abstrak.

kev
sumber
Apakah Anda memerlukan kelas penuh untuk menjadi abstrak atau hanya beberapa metode di dalamnya? Lihat jawabannya di sini untuk metode dan properti tunggal. stackoverflow.com/a/39038828/2435872 . Di Java, Anda dapat ave kelas abstrak, yang tidak memiliki metode abstrak. Fitur khusus itu tidak disediakan oleh Swift.
jboi

Jawaban:

174

Tidak ada kelas abstrak di Swift (seperti Objective-C). Taruhan terbaik Anda adalah menggunakan Protokol , yang seperti Java Interface.

Dengan Swift 2.0, Anda kemudian dapat menambahkan implementasi metode dan implementasi properti yang dihitung menggunakan ekstensi protokol. Satu-satunya batasan Anda adalah Anda tidak dapat memberikan variabel atau konstanta anggota dan tidak ada pengiriman dinamis .

Contoh dari teknik ini adalah:

protocol Employee {
    var annualSalary: Int {get}
}

extension Employee {
    var biweeklySalary: Int {
        return self.annualSalary / 26
    }

    func logSalary() {
        print("$\(self.annualSalary) per year or $\(self.biweeklySalary) biweekly")
    }
}

struct SoftwareEngineer: Employee {
    var annualSalary: Int

    func logSalary() {
        print("overridden")
    }
}

let sarah = SoftwareEngineer(annualSalary: 100000)
sarah.logSalary() // prints: overridden
(sarah as Employee).logSalary() // prints: $100000 per year or $3846 biweekly

Perhatikan bahwa ini menyediakan "kelas abstrak" seperti fitur bahkan untuk struct, tetapi kelas juga dapat mengimplementasikan protokol yang sama.

Juga perhatikan bahwa setiap kelas atau struct yang mengimplementasikan protokol Karyawan harus menyatakan properti AnnualSalary lagi.

Yang terpenting, perhatikan bahwa tidak ada pengiriman dinamis . Ketika logSalarydipanggil pada instance yang disimpan sebagai SoftwareEngineerpanggilan metode versi ditimpa. Ketika logSalarydipanggil pada instance setelah di-cast ke Employee, ia memanggil implementasi asli (itu tidak secara dinamis mengirimkan ke versi yang diganti meskipun instance sebenarnya a Software Engineer.

Untuk informasi lebih lanjut, lihat video WWDC yang luar biasa tentang fitur itu: Membangun Aplikasi yang Lebih Baik dengan Jenis Nilai di Swift

drewag
sumber
3
protocol Animal { var property : Int { get set } }. Anda juga dapat meninggalkan set jika Anda tidak ingin properti memiliki setter
drewag
3
Saya pikir video wwdc ini bahkan lebih relevan
Mario Zannone
2
@MarioZannone video itu baru saja membuat saya terlena dan membuat saya jatuh cinta pada Swift.
Scott H
3
Jika Anda hanya menambahkan func logSalary()ke deklarasi protokol Karyawan, contoh mencetak overriddenuntuk kedua panggilan logSalary(). Ini ada di Swift 3.1. Dengan demikian Anda mendapatkan manfaat polimorfisme. Metode yang benar disebut dalam kedua kasus.
Mike Taverne
1
Aturan tentang pengiriman dinamis adalah ini ... jika metode ini didefinisikan hanya dalam ekstensi, maka itu dikirim secara statis. Jika itu juga didefinisikan dalam protokol yang Anda rentangkan, maka itu dikirim secara dinamis. Tidak perlu untuk runtime Objective-C. Ini adalah perilaku Swift yang murni.
Mark A. Donohoe
47

Perhatikan bahwa jawaban ini ditargetkan pada Swift 2.0 ke atas

Anda dapat mencapai perilaku yang sama dengan protokol dan ekstensi protokol.

Pertama, Anda menulis protokol yang berfungsi sebagai antarmuka untuk semua metode yang harus diimplementasikan dalam semua jenis yang sesuai dengannya.

protocol Drivable {
    var speed: Float { get set }
}

Kemudian Anda bisa menambahkan perilaku default ke semua jenis yang sesuai dengannya

extension Drivable {
    func accelerate(by: Float) {
        speed += by
    }
}

Anda sekarang dapat membuat tipe baru dengan mengimplementasikan Drivable.

struct Car: Drivable {
    var speed: Float = 0.0
    init() {}
}

let c = Car()
c.accelerate(10)

Jadi pada dasarnya Anda mendapatkan:

  1. Kompilasi pemeriksaan waktu yang menjamin bahwa semua Drivableimplementasispeed
  2. Anda dapat menerapkan perilaku default untuk semua jenis yang sesuai dengan Drivable( accelerate)
  3. Drivable dijamin tidak akan dipakai karena itu hanya protokol

Model ini sebenarnya berperilaku jauh lebih seperti ciri-ciri, yang berarti Anda dapat menyesuaikan diri dengan beberapa protokol dan mengambil implementasi default dari salah satu dari mereka, sedangkan dengan superclass abstrak Anda terbatas pada hirarki kelas yang sederhana.

IluTov
sumber
Namun, tidak selalu ada kemungkinan untuk memperpanjang beberapa protokol, misalnya UICollectionViewDatasource,. Saya ingin menghapus semua boilerplate dan merangkumnya dalam protokol / ekstensi terpisah dan kemudian digunakan kembali oleh beberapa kelas. Faktanya, pola templat akan sempurna di sini, tapi ...
Richard Topchii
1
Anda tidak dapat menimpa celhapusperangkat˚ dalam ˚Car˚. Jika Anda melakukannya, implementasi di ˚ekstensi Driveable˚ masih dipanggil tanpa peringatan kompilator. Sangat berbeda dengan kelas abstrak Java
Gerd Castan
@ GerdCastan Benar, ekstensi protokol tidak mendukung pengiriman dinamis.
IluTov
15

Saya pikir ini adalah yang terdekat dengan Java abstractatau C # abstract:

class AbstractClass {

    private init() {

    }
}

Perhatikan bahwa, agar privatepengubah berfungsi, Anda harus mendefinisikan kelas ini dalam file Swift yang terpisah.

EDIT: Namun, kode ini tidak memungkinkan untuk mendeklarasikan metode abstrak dan karenanya memaksakan implementasinya.

Teejay
sumber
4
Namun, ini tidak memaksa subclass untuk mengesampingkan fungsi sementara juga memiliki implementasi dasar dari fungsi itu di kelas induk.
Matthew Quiros
Di C #, jika Anda mengimplementasikan fungsi dalam kelas dasar abstrak, Anda tidak dipaksa untuk mengimplementasikannya dalam subkelasnya. Namun, kode ini tidak memungkinkan Anda untuk mendeklarasikan metode abstrak untuk memaksa override.
Teejay
Katakanlah bahwa ConcreteClass subclass bahwa AbstractClass. Bagaimana Anda instantiate ConcreteClass?
Javier Cadiz
2
ConcreteClass harus memiliki konstruktor publik. Anda mungkin memerlukan konstruktor yang dilindungi di AbstractClass, kecuali mereka berada di file yang sama. Sesuai apa yang saya ingat, pengubah akses yang dilindungi tidak ada di Swift. Jadi solusinya adalah mendeklarasikan ConcreteClass dalam file yang sama.
Teejay
13

Cara paling sederhana adalah dengan menggunakan panggilan ke fatalError("Not Implemented")dalam metode abstrak (bukan variabel) pada ekstensi protokol.

protocol MyInterface {
    func myMethod() -> String
}


extension MyInterface {

    func myMethod() -> String {
        fatalError("Not Implemented")
    }

}

class MyConcreteClass: MyInterface {

    func myMethod() -> String {
        return "The output"
    }

}

MyConcreteClass().myMethod()
Carlos García
sumber
Ini jawaban yang bagus. Saya tidak berpikir itu akan berhasil jika Anda menelepon (MyConcreteClass() as MyInterface).myMethod()tetapi itu berhasil! Kuncinya termasuk myMethoddalam deklarasi protokol; jika tidak panggilan akan crash.
Mike Taverne
11

Setelah saya berjuang selama beberapa minggu, saya akhirnya menyadari bagaimana menerjemahkan kelas abstrak Java / PHP ke Swift:

public class AbstractClass: NSObject {

    internal override init(){}

    public func getFoodToEat()->String
    {
        if(self._iAmHungry())
        {
            return self._myFavoriteFood();
        }else{
            return "";
        }
    }

    private func _myFavoriteFood()->String
    {
        return "Sandwich";
    }

    internal func _iAmHungry()->Bool
    {
        fatalError(__FUNCTION__ + "Must be overridden");
        return false;
    }
}

public class ConcreteClass: AbstractClass, IConcreteClass {

    private var _hungry: Bool = false;

    public override init() {
        super.init();
    }

    public func starve()->Void
    {
        self._hungry = true;
    }

    public override func _iAmHungry()->Bool
    {
        return self._hungry;
    }
}

public protocol IConcreteClass
{
    func _iAmHungry()->Bool;
}

class ConcreteClassTest: XCTestCase {

    func testExample() {

        var concreteClass: ConcreteClass = ConcreteClass();

        XCTAssertEqual("", concreteClass.getFoodToEat());

        concreteClass.starve();

        XCTAssertEqual("Sandwich", concreteClass.getFoodToEat());
    }
}

Namun saya pikir Apple tidak menerapkan kelas abstrak karena umumnya menggunakan pola + protokol delegasi sebagai gantinya. Misalnya pola yang sama di atas akan lebih baik dilakukan seperti ini:

import UIKit

    public class GoldenSpoonChild
    {
        private var delegate: IStomach!;

        internal init(){}

        internal func setup(delegate: IStomach)
        {
            self.delegate = delegate;
        }

        public func getFoodToEat()->String
        {
            if(self.delegate.iAmHungry())
            {
                return self._myFavoriteFood();
            }else{
                return "";
            }
        }

        private func _myFavoriteFood()->String
        {
            return "Sandwich";
        }
    }

    public class Mother: GoldenSpoonChild, IStomach
    {

        private var _hungry: Bool = false;

        public override init()
        {
            super.init();
            super.setup(self);
        }

        public func makeFamilyHungry()->Void
        {
            self._hungry = true;
        }

        public func iAmHungry()->Bool
        {
            return self._hungry;
        }
    }

    protocol IStomach
    {
        func iAmHungry()->Bool;
    }

    class DelegateTest: XCTestCase {

        func testGetFood() {

            var concreteClass: Mother = Mother();

            XCTAssertEqual("", concreteClass.getFoodToEat());

            concreteClass.makeFamilyHungry();

            XCTAssertEqual("Sandwich", concreteClass.getFoodToEat());
        }
    }

Saya membutuhkan pola seperti ini karena saya ingin menyamakan beberapa metode di UITableViewController seperti viewWillAppear dll. Apakah ini membantu?

Josh Woodcock
sumber
1
+1 Sedang merencanakan untuk melakukan pendekatan yang sama persis seperti yang Anda sebutkan pertama; pointer menarik ke pola delegasi.
Angad
Juga, akan membantu jika kedua contoh Anda menggunakan use-case yang sama. GoldenSpoonChild adalah nama yang agak membingungkan, terutama mengingat bahwa Bunda tampaknya memperpanjangnya.
Angad
@Angad Pola delegasi adalah use case yang sama, namun itu bukan terjemahan; itu pola yang berbeda sehingga harus mengambil perspektif yang berbeda.
Josh Woodcock
8

Ada cara untuk mensimulasikan kelas abstrak menggunakan Protokol. Ini adalah sebuah contoh:

protocol MyProtocol {
   func doIt()
}

class BaseClass {
    weak var myDelegate: MyProtocol?

    init() {
        ...
    }

    func myFunc() {
        ...
        self.myDelegate?.doIt()
        ...
    }
}

class ChildClass: BaseClass, MyProtocol {
    override init(){
        super.init()
        self.myDelegate = self
    }

    func doIt() {
        // Custom implementation
    }
}
David Seca
sumber
1

Satu lagi cara Anda bisa mengimplementasikan kelas abstrak adalah dengan memblokir initializer. Saya telah melakukannya dengan cara ini:

class Element:CALayer { // IT'S ABSTRACT CLASS

    override init(){ 
        super.init()
        if self.dynamicType === Element.self {
        fatalError("Element is abstract class, do not try to create instance of this class")
        }
    }
}
Alexey Yarmolovich
sumber
4
Ini tidak memberikan jaminan dan / atau cek. Meledakkan selama runtime adalah cara yang buruk untuk menegakkan aturan. Lebih baik memiliki init sebagai pribadi.
Морт
Kelas abstrak juga harus memiliki dukungan untuk metode abstrak.
Cristik
@ Christik Saya menunjukkan ide utama, itu bukan solusi lengkap. Dengan cara ini Anda bisa tidak menyukai 80% jawaban karena tidak cukup detail untuk situasi Anda
Alexey Yarmolovich
1
@AlexeyYarmolovich yang bilang aku tidak suka 80% dari jawabannya? :) Sambil bercanda, saya menyarankan agar contoh Anda dapat ditingkatkan, ini akan membantu pembaca lain, dan akan membantu Anda dengan mendapatkan upvotes.
Cristik
0

Saya mencoba membuat Weatherkelas abstrak, tetapi menggunakan protokol tidak ideal karena saya harus menulis initmetode yang sama berulang kali. Memperluas protokol dan menulis initmetode memiliki masalah, terutama karena saya menggunakan NSObjectpenyesuaian NSCoding.

Jadi saya datang dengan ini untuk NSCodingkesesuaian:

required init?(coder aDecoder: NSCoder) {
    guard type(of: self) != Weather.self else {
        fatalError("<Weather> This is an abstract class. Use a subclass of `Weather`.")
    }
    // Initialize...
}        

Adapun init:

fileprivate init(param: Any...) {
    // Initialize
}
funct7
sumber
0

Pindahkan semua referensi ke properti abstrak dan metode kelas Base ke implementasi ekstensi protokol, di mana Self membatasi ke kelas Base. Anda akan mendapatkan akses ke semua metode dan properti kelas Base. Selain itu kompiler memeriksa implementasi metode dan properti abstrak dalam protokol untuk kelas turunan

protocol Commom:class{
  var tableView:UITableView {get};
  func update();
}

class Base{
   var total:Int = 0;
}

extension Common where Self:Base{
   func update(){
     total += 1;
     tableView.reloadData();
   }
} 

class Derived:Base,Common{
  var tableView:UITableView{
    return owner.tableView;
  }
}
john07
sumber
0

Dengan batasan tidak ada pengiriman dinamis, Anda dapat melakukan sesuatu seperti ini:

import Foundation

protocol foo {

    static var instance: foo? { get }
    func prt()

}

extension foo {

    func prt() {
        if Thread.callStackSymbols.count > 30 {
            print("super")
        } else {
            Self.instance?.prt()
        }
    }

}

class foo1 : foo {

    static var instance : foo? = nil

    init() {
        foo1.instance = self
    }

    func prt() {
        print("foo1")
    }

}

class foo2 : foo {

    static var instance : foo? = nil

    init() {
        foo2.instance = self
    }

    func prt() {
        print("foo2")
    }

}

class foo3 : foo {

    static var instance : foo? = nil

    init() {
        foo3.instance = self
    }

}

var f1 : foo = foo1()
f1.prt()
var f2 : foo = foo2()
f2.prt()
var f3 : foo = foo3()
f3.prt()
Christo Smal
sumber