Abstrakte Klassen in schneller Sprache

140

Gibt es eine Möglichkeit, eine abstrakte Klasse in der Swift-Sprache zu erstellen, oder ist dies eine Einschränkung wie bei Objective-C? Ich möchte eine abstrakte Klasse erstellen, die mit dem vergleichbar ist, was Java als abstrakte Klasse definiert.

kev
quelle
Benötigen Sie die vollständige Klasse, um abstrakt zu sein, oder nur einige Methoden? In der Antwort finden Sie einzelne Methoden und Eigenschaften. stackoverflow.com/a/39038828/2435872 . In Java können Sie abstrakte Klassen erstellen, für die keine der Methoden abstrakt ist. Diese Besonderheit wird von Swift nicht bereitgestellt.
Jboi

Antworten:

174

In Swift gibt es keine abstrakten Klassen (genau wie in Objective-C). Am besten verwenden Sie ein Protokoll , das einer Java-Schnittstelle ähnelt.

Mit Swift 2.0 können Sie dann Methodenimplementierungen und berechnete Eigenschaftsimplementierungen mithilfe von Protokollerweiterungen hinzufügen. Ihre einzigen Einschränkungen bestehen darin, dass Sie keine Mitgliedsvariablen oder Konstanten bereitstellen können und kein dynamischer Versand erfolgt .

Ein Beispiel für diese Technik wäre:

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

Beachten Sie, dass dies auch für Strukturen "abstrakte Klassen" -ähnliche Funktionen bietet, Klassen jedoch auch dasselbe Protokoll implementieren können.

Beachten Sie auch, dass jede Klasse oder Struktur, die das Mitarbeiterprotokoll implementiert, die Eigenschaft annualSalary erneut deklarieren muss.

Beachten Sie vor allem, dass kein dynamischer Versand erfolgt . Wenn logSalaryfür die Instanz aufgerufen wird, die als gespeichert ist, wird SoftwareEngineerdie überschriebene Version der Methode aufgerufen . WannlogSalary die Instanz aufgerufen wird, nachdem sie in eine umgewandelt wurde Employee, wird die ursprüngliche Implementierung aufgerufen (sie wird nicht dynamisch an die überschriebene Version gesendet, obwohl die Instanz tatsächlich eine istSoftware Engineer .

Weitere Informationen finden Sie in einem großartigen WWDC-Video zu dieser Funktion: Bessere Apps mit Werttypen in Swift erstellen

Drawag
quelle
3
protocol Animal { var property : Int { get set } }. Sie können das Set auch
weglassen
3
Ich denke, dieses wwdc-Video ist noch relevanter
Mario Zannone
2
@MarioZannone dieses Video hat mich einfach umgehauen und mich in Swift verliebt.
Scott H
3
Wenn Sie nur func logSalary()die Mitarbeiterprotokolldeklaration hinzufügen , wird das Beispiel overriddenfür beide Aufrufe an gedruckt logSalary(). Dies ist in Swift 3.1. So erhalten Sie die Vorteile des Polymorphismus. In beiden Fällen wird die richtige Methode aufgerufen.
Mike Taverne
1
Die Regel für den dynamischen Versand lautet: Wenn die Methode nur in der Erweiterung definiert ist, wird sie statisch versendet. Wenn es auch in dem Protokoll definiert ist, das Sie erweitern, wird es dynamisch versendet. Keine Objective-C-Laufzeiten erforderlich. Dies ist reines Swift-Verhalten.
Mark A. Donohoe
47

Beachten Sie, dass diese Antwort auf Swift 2.0 und höher ausgerichtet ist

Sie können dasselbe Verhalten mit Protokollen und Protokollerweiterungen erzielen.

Zunächst schreiben Sie ein Protokoll, das als Schnittstelle für alle Methoden fungiert, die in allen entsprechenden Typen implementiert werden müssen.

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

Anschließend können Sie allen entsprechenden Typen ein Standardverhalten hinzufügen

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

Sie können jetzt neue Typen erstellen, indem Sie implementieren Drivable.

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

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

Im Grunde genommen bekommen Sie also:

  1. Kompilieren Sie Zeitprüfungen, die dies garantieren Drivable implementiert werdenspeed
  2. Sie können das Standardverhalten für alle Typen implementieren, die Drivable(accelerate ) entsprechen
  3. Drivable wird garantiert nicht instanziiert, da es sich nur um ein Protokoll handelt

Dieses Modell verhält sich tatsächlich viel mehr wie Merkmale, dh Sie können sich an mehrere Protokolle anpassen und Standardimplementierungen von jedem übernehmen, während Sie sich bei einer abstrakten Oberklasse auf eine einfache Klassenhierarchie beschränken.

IluTov
quelle
Dennoch gibt es nicht immer die Möglichkeit, beispielsweise einige Protokolle zu erweitern UICollectionViewDatasource. Ich möchte das gesamte Boilerplate entfernen und es in einem separaten Protokoll / einer separaten Erweiterung kapseln und dann von mehreren Klassen wiederverwenden. In der Tat wäre das Vorlagenmuster hier perfekt, aber ...
Richard Topchii
1
Sie können "Beschleunigen" in "Auto" nicht überschreiben. In diesem Fall wird die Implementierung in ˚extentsion Driveable˚ weiterhin ohne Compiler-Warnung aufgerufen. Ganz anders als eine abstrakte Java-Klasse
Gerd Castan
@GerdCastan Richtig, Protokollerweiterungen unterstützen keinen dynamischen Versand.
IluTov
15

Ich denke, dies ist am nächsten an Java abstractoder C # abstract:

class AbstractClass {

    private init() {

    }
}

Beachten Sie, dass privateSie diese Klasse in einer separaten Swift-Datei definieren müssen, damit die Modifikatoren funktionieren.

BEARBEITEN: Dieser Code erlaubt es jedoch nicht, eine abstrakte Methode zu deklarieren und somit deren Implementierung zu erzwingen.

Teejay
quelle
4
Dies zwingt eine Unterklasse jedoch nicht dazu , eine Funktion zu überschreiben, während gleichzeitig eine Basisimplementierung dieser Funktion in der übergeordneten Klasse vorhanden ist.
Matthew Quiros
Wenn Sie in C # eine Funktion in einer abstrakten Basisklasse implementieren, müssen Sie sie nicht in ihren Unterklassen implementieren. Mit diesem Code können Sie jedoch keine abstrakte Methode deklarieren, um das Überschreiben zu erzwingen.
Teejay
Nehmen wir an, dass ConcreteClass diese AbstractClass-Unterklasse ist. Wie instanziieren Sie ConcreteClass?
Javier Cadiz
2
ConcreteClass sollte einen öffentlichen Konstruktor haben. Sie benötigen wahrscheinlich einen geschützten Konstruktor in AbstractClass, es sei denn, sie befinden sich in derselben Datei. Soweit ich mich erinnere, gibt es in Swift keinen Modifikator für geschützten Zugriff. Die Lösung besteht also darin, ConcreteClass in derselben Datei zu deklarieren.
Teejay
13

Am einfachsten ist es, einen Aufruf fatalError("Not Implemented")der abstrakten Methode (nicht variabel) in der Protokollerweiterung zu verwenden.

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
quelle
Dies ist eine großartige Antwort. Ich hätte nicht gedacht, dass es funktionieren würde, wenn Sie anrufen würden, (MyConcreteClass() as MyInterface).myMethod()aber es tut es! Der Schlüssel ist myMethodin der Protokolldeklaration enthalten. Andernfalls stürzt der Anruf ab.
Mike Taverne
11

Nachdem ich einige Wochen lang Probleme hatte, wurde mir endlich klar, wie man eine abstrakte Java / PHP-Klasse in Swift übersetzt:

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());
    }
}

Ich denke jedoch, dass Apple keine abstrakten Klassen implementiert hat, da es im Allgemeinen stattdessen das Delegate + -Protokollmuster verwendet. Zum Beispiel wäre das gleiche Muster oben besser wie folgt:

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());
        }
    }

Ich brauchte diese Art von Muster, weil ich einige Methoden in UITableViewController wie viewWillAppear usw. vereinheitlichen wollte. War dies hilfreich?

Josh Woodcock
quelle
1
+1 plante genau den gleichen Ansatz, den Sie zuerst erwähnt haben; interessanter Zeiger auf das Delegationsmuster.
Angad
Es wäre auch hilfreich, wenn beide Beispiele denselben Anwendungsfall hätten. GoldenSpoonChild ist ein etwas verwirrender Name, vor allem, weil Mutter ihn zu erweitern scheint.
Angad
@Angad Das Delegatenmuster ist der gleiche Anwendungsfall, jedoch keine Übersetzung. Es ist ein anderes Muster, also muss es eine andere Perspektive einnehmen.
Josh Woodcock
8

Es gibt eine Möglichkeit, abstrakte Klassen mithilfe von Protokollen zu simulieren. Dies ist ein Beispiel:

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
quelle
1

Eine weitere Möglichkeit, eine abstrakte Klasse zu implementieren, besteht darin, den Initialisierer zu blockieren. Ich habe es so gemacht:

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
quelle
4
Dies bietet keine Garantien und / oder Kontrollen. Das Sprengen zur Laufzeit ist ein schlechter Weg, um Regeln durchzusetzen. Es ist besser, den Init als privat zu haben.
29орт
Abstrakte Klassen sollten auch abstrakte Methoden unterstützen.
Cristik
@Cristik Ich habe die Hauptidee gezeigt, es ist keine vollständige Lösung. Auf diese Weise können Sie 80% der Antworten nicht mögen, weil sie nicht detailliert genug für Ihre Situation sind
Alexey Yarmolovich
1
@AlexeyYarmolovich wer sagt, dass ich 80% der Antworten nicht ablehne? :) Scherz beiseite, ich schlug vor, dass Ihr Beispiel verbessert werden kann, dies wird anderen Lesern helfen und Ihnen helfen, indem Sie positive Stimmen erhalten.
Cristik
0

Ich habe versucht, eine Weatherabstrakte Klasse zu erstellen, aber die Verwendung von Protokollen war nicht ideal, da ich initimmer wieder dieselben Methoden schreiben musste. Das Erweitern des Protokolls und das Schreiben einer initMethode hatte Probleme, insbesondere da ich NSObjectKonformität mit verwendet habe NSCoding.

Also habe ich mir das für die NSCodingKonformität ausgedacht:

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...
}        

Wie für init:

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

Verschieben Sie alle Verweise auf abstrakte Eigenschaften und Methoden der Basisklasse in die Implementierung der Protokollerweiterung, wobei Self auf die Basisklasse beschränkt ist. Sie erhalten Zugriff auf alle Methoden und Eigenschaften der Basisklasse. Zusätzlich überprüft der Compiler die Implementierung abstrakter Methoden und Eigenschaften im Protokoll für abgeleitete Klassen

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
quelle
0

Mit der Einschränkung, dass kein dynamischer Versand erfolgt, können Sie Folgendes tun:

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
quelle