Abstrakte Funktionen in schneller Sprache


127

Ich möchte eine abstrakte Funktion in schneller Sprache erstellen. Ist es möglich?

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

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

Dies ist ziemlich nah an Ihrer anderen Frage, aber die Antwort hier scheint etwas besser zu sein.
David Berry

Die Fragen sind ähnlich, aber die Lösungen sind sehr unterschiedlich, da eine abstrakte Klasse andere Anwendungsfälle hat als eine abstrakte Funktion.
Kev

Ja, warum ich auch nicht für das Schließen gestimmt habe, aber die Antworten dort werden nicht nützlich sein, außer "du kannst nicht". Hier hast du die beste verfügbare Antwort :)
David Berry

Antworten:


198

In Swift gibt es kein abstraktes Konzept (wie Objective-C), aber Sie können dies tun:

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

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

13
Alternativ können Sie verwenden assert(false, "This method must be overriden by the subclass").
Erik

20
oderfatalError("This method must be overridden")
Nathan

5
Assertions erfordern eine return-Anweisung, wenn eine Methode einen return-Typ hat. Ich denke, fatalError () ist aus diesem einen Grund besser
André Fratelli

6
Es gibt auch preconditionFailure("Bla bla bla")in Swift, das in Release-Builds ausgeführt wird und die Notwendigkeit einer return-Anweisung beseitigt. EDIT: Nur fand heraus , dass diese Methode im Grunde gleich ist , fatalError()aber es ist der richtige Weg , mehr (Bessere Dokumentation, in Swift eingeführt zusammen mit precondition(), assert()und assertionFailure(), lesen Sie hier )
Kametrixom

2
Was ist, wenn die Funktion einen Rückgabetyp hat?
LoveMeow

36

Was Sie wollen, ist keine Basisklasse, sondern ein Protokoll.

protocol MyProtocol {
    func abstractFunction()
}

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

Wenn Sie in Ihrer Klasse keine abstractFunction angeben, liegt ein Fehler vor.

Wenn Sie die Basisklasse für ein anderes Verhalten noch benötigen, können Sie Folgendes tun:

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

23
Das funktioniert nicht ganz. Wenn BaseClass diese leeren Funktionen aufrufen möchte, müsste es auch das Protokoll implementieren und dann die Funktionen implementieren. Außerdem ist die Unterklasse immer noch nicht gezwungen, die Funktionen zur Kompilierungszeit zu implementieren, und Sie sind auch nicht gezwungen, das Protokoll zu implementieren.
Bandejapaisa

5
Das ist mein Punkt ... BaseClass hat nichts mit dem Protokoll zu tun, und um sich wie eine abstrakte Klasse zu verhalten, sollte es etwas damit zu tun haben. Um zu verdeutlichen, was ich geschrieben habe "Wenn BaseClass diese leeren Funktionen aufrufen möchte, müsste es auch das Protokoll implementieren, das Sie zur Implementierung der Funktionen zwingen würde - was dann dazu führt, dass die Unterklasse nicht gezwungen wird, die Funktionen zur Kompilierungszeit zu implementieren." weil die BaseClass es bereits getan hat ".
Bandejapaisa

4
Das hängt wirklich davon ab, wie Sie "abstrakt" definieren. Der springende Punkt einer abstrakten Funktion ist, dass Sie sie einer Klasse zuordnen können, die normale Klassensachen ausführen kann. Der Grund, warum ich dies möchte, ist beispielsweise, dass ich ein statisches Element definieren muss, das mit Protokollen scheinbar nicht möglich ist. Daher fällt es mir schwer zu erkennen, dass dies den nützlichen Punkt einer abstrakten Funktion erfüllt.
Welpe

3
Ich denke, die Besorgnis über die oben stehenden Kommentare ist, dass dies nicht dem Liskov-Substitutionsprinzip entspricht, das besagt, dass "Objekte in einem Programm durch Instanzen ihrer Subtypen ersetzt werden sollten, ohne die Richtigkeit dieses Programms zu ändern." Es wird angenommen, dass dies ein abstrakter Typ ist. Dies ist besonders wichtig im Fall des Factory-Methodenmusters, bei dem die Basisklasse für das Erstellen und Speichern von Eigenschaften verantwortlich ist, die von der Unterklasse stammen.
David James

2
Eine abstrakte Basisklasse und ein Protokoll sind nicht dasselbe.
Shoerob

29

Ich portiere eine ganze Menge Code von einer Plattform, die abstrakte Basisklassen unterstützt, nach Swift und laufe viel darauf ein. Wenn Sie wirklich die Funktionalität einer abstrakten Basisklasse wünschen, bedeutet dies, dass diese Klasse sowohl als Implementierung einer gemeinsam genutzten basierten Klassenfunktionalität dient (andernfalls wäre es nur eine Schnittstelle / ein Protokoll), als auch Methoden definiert, die von implementiert werden müssen abgeleitete Klassen.

Dazu benötigen Sie in Swift ein Protokoll und eine Basisklasse.

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

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

Beachten Sie, dass die Basisklasse die gemeinsam genutzten Methoden implementiert, das Protokoll jedoch nicht implementiert (da nicht alle Methoden implementiert werden).

Dann in der abgeleiteten Klasse:

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

Die abgeleitete Klasse erbt sharedFunction von der Basisklasse, wodurch dieser Teil des Protokolls erfüllt wird, und das Protokoll erfordert weiterhin, dass die abgeleitete Klasse abstractFunction implementiert.

Der einzige wirkliche Nachteil dieser Methode besteht darin, dass Sie, da die Basisklasse das Protokoll nicht implementiert, eine Basisklassenmethode, die Zugriff auf eine Protokolleigenschaft / -methode benötigt, diese in der abgeleiteten Klasse überschreiben und von dort aus aufrufen müssen Die Basisklasse (über Super) wird übergeben, selfsodass die Basisklasse eine Instanz des Protokolls hat, mit dem sie ihre Arbeit erledigen kann.

Angenommen, sharedFunction muss abstraktFunction aufrufen. Das Protokoll würde gleich bleiben und die Klassen würden nun so aussehen:

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

Jetzt erfüllt die sharedFunction aus der abgeleiteten Klasse diesen Teil des Protokolls, aber die abgeleitete Klasse kann die Basisklassenlogik immer noch auf einigermaßen einfache Weise gemeinsam nutzen.


4
Großes Verständnis und gute Tiefe in Bezug auf "Die Basisklasse implementiert das Protokoll nicht" ... das ist eine Art Punkt einer abstrakten Klasse. Ich bin verblüfft, dass diese grundlegende OO-Funktion fehlt, aber andererseits bin ich auf Java aufgewachsen.
Dan Rosenstark

2
Die Implementierung ermöglicht jedoch keine nahtlose Implementierung der Template Function-Methode, bei der die Template-Methode eine große Anzahl von abstrakten Methoden aufruft, die in den Unterklassen implementiert sind. In diesem Fall sollten Sie sie sowohl als normale Methoden in der Superklasse als auch als Methoden im Protokoll und erneut als Implementierungen in der Unterklasse schreiben. In der Praxis muss man dreimal das gleiche Zeug schreiben und sich nur auf die Übersteuerungsprüfung verlassen, um sicherzugehen, dass keine katastrophalen Rechtschreibfehler auftreten! Ich hoffe wirklich, dass Swift jetzt für Entwickler offen ist und eine vollwertige abstrakte Funktion eingeführt wird.
Fabrizio Bartolomucci

1
So viel Zeit und Code könnten durch die Einführung des Schlüsselworts "protected" eingespart werden.
Shoerob

23

Dies scheint die "offizielle" Art zu sein, wie Apple mit abstrakten Methoden in UIKit umgeht. Schauen Sie sich an UITableViewControllerund wie es funktioniert UITableViewDelegate. Eines der allerersten Dinge, die Sie tun, ist das Hinzufügen einer Zeile : delegate = self. Das ist genau der Trick.

1. Fügen Sie die abstrakte Methode in ein Protokoll ein
protocol AbstractMethodsForClassX {
    func abstractMethod() -> String
}
2. Schreiben Sie Ihre Basisklasse
/// 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. Schreiben Sie die Unterklasse (n).
/// 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!"}
}
Hier erfahren Sie, wie Sie all das nutzen können
let x = ClassX()
x.doSomethingWithAbstractMethod()

Wenden Sie sich an Playground, um die Ausgabe zu sehen.

Einige Bemerkungen

  • Zunächst wurden bereits viele Antworten gegeben. Ich hoffe, jemand findet den ganzen Weg bis zu diesem.
  • Die Frage war tatsächlich, ein Muster zu finden, das Folgendes implementiert:
    • Eine Klasse ruft eine Methode auf, die in einer ihrer abgeleiteten Unterklassen implementiert werden muss (überschrieben).
    • Wenn die Methode in der Unterklasse nicht überschrieben wird, wird im besten Fall während der Kompilierungszeit ein Fehler angezeigt
  • Die Sache mit abstrakten Methoden ist, dass sie eine Mischung aus einer Schnittstellendefinition und einem Teil einer tatsächlichen Implementierung in der Basisklasse sind. Beides zur selben Zeit. Da Swift sehr neu und sehr klar definiert ist, hat es (noch) keine solche Bequemlichkeit, sondern "unreine" Konzepte.
  • Für mich (einen armen alten Java-Typ) tritt dieses Problem von Zeit zu Zeit auf. Ich habe alle Antworten in diesem Beitrag durchgelesen und dieses Mal denke ich, dass ich ein Muster gefunden habe, das machbar aussieht - zumindest für mich.
  • Update : Es sieht so aus, als würden die UIKit-Implementierer bei Apple dasselbe Muster verwenden. UITableViewControllerimplementiert UITableViewDelegate, muss aber weiterhin als Delegat registriert werden, indem die delegateEigenschaft explizit festgelegt wird .
  • Dies alles wird auf dem Spielplatz von Xcode 7.3.1 getestet

Vielleicht nicht perfekt, weil ich Probleme habe, die Schnittstelle der Klasse vor anderen Klassen zu verbergen, aber das ist genug, um die klassische Factory-Methode in Swift zu implementieren.
Tomasz Nazarenko

Hmmm. Ich mag das Aussehen dieser Antwort viel mehr, bin mir aber auch nicht sicher, ob sie geeignet ist. Das heißt, ich habe einen ViewController, der Informationen für eine Handvoll verschiedener Objekttypen anzeigen kann, aber der Zweck der Anzeige dieser Informationen ist derselbe, mit denselben Methoden, aber das Abrufen und Zusammenstellen der Informationen basierend auf dem Objekt . Ich habe also eine übergeordnete Delegatenklasse für diese VC und eine Unterklasse dieses Delegaten für jeden Objekttyp. Ich instanziiere einen Delegaten basierend auf dem übergebenen Objekt. In einem Beispiel sind in jedem Objekt einige relevante Kommentare gespeichert.
Jake T.

Ich habe also eine getCommentsMethode für den übergeordneten Delegaten. Es gibt eine Handvoll Kommentartypen, und ich möchte diejenigen, die für jedes Objekt relevant sind. Ich möchte also, dass die Unterklassen nicht kompiliert werden, wenn ich diese delegate.getComments()Methode nicht überschreibe. Der VC weiß nur, dass er ein ParentDelegateObjekt namens delegateoder BaseClassXin Ihrem Beispiel hat, hat aber BaseClassXnicht die abstrahierte Methode. Ich würde den VC brauchen, um genau zu wissen, dass er den verwendet SubclassedDelegate.
Jake T.

Ich hoffe ich verstehe dich richtig. Wenn nicht, haben Sie etwas dagegen, eine neue Frage zu stellen, in der Sie Code hinzufügen können? Ich denke, Sie müssen nur eine if-Anweisung in "doSomethingWithAbstractMethod" haben, um zu überprüfen, ob der Delegat festgelegt ist oder nicht.
Jboi

Es ist erwähnenswert, dass das Zuweisen von sich selbst zum Delegierten einen Aufbewahrungszyklus erzeugt. Vielleicht ist es besser, es als schwach zu deklarieren (was
normalerweise

7

Eine Möglichkeit, dies zu tun, besteht darin, einen optionalen Abschluss zu verwenden, der in der Basisklasse definiert ist, und die Kinder können wählen, ob er implementiert werden soll oder nicht.

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

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

Was mir an diesem Ansatz gefällt, ist, dass ich nicht daran denken muss, ein Protokoll in der ererbenden Klasse zu implementieren. Ich habe eine Basisklasse für meine ViewController. Auf diese Weise erzwinge ich ViewController-spezifische, optionale Funktionen (z. B. App wurde aktiv usw.), die möglicherweise von der Basisklassenfunktionalität aufgerufen werden.
ProgrammierTier

6

Nun, ich weiß, dass ich zu spät zum Spiel komme und dass ich möglicherweise die Änderungen ausnutzen werde, die stattgefunden haben. Das tut mir leid.

Auf jeden Fall möchte ich meine Antwort einbringen, weil ich es liebe zu testen und die Lösungen mit fatalError()AFAIK nicht testbar sind und diejenigen mit Ausnahmen viel schwieriger zu testen sind.

Ich würde vorschlagen, einen schnelleren Ansatz zu verwenden. Ihr Ziel ist es, eine Abstraktion zu definieren, die einige gemeinsame Details aufweist, die jedoch nicht vollständig definiert ist, dh die abstrakte (n) Methode (n). Verwenden Sie ein Protokoll, das alle erwarteten Methoden in der Abstraktion definiert, sowohl die definierten als auch die nicht definierten. Erstellen Sie dann eine Protokollerweiterung, die die in Ihrem Fall definierten Methoden implementiert. Schließlich muss jede abgeleitete Klasse das Protokoll implementieren, dh alle Methoden, aber diejenigen, die Teil der Protokollerweiterung sind, haben bereits ihre Implementierung.

Erweitern Sie Ihr Beispiel um eine konkrete Funktion:

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

Beachten Sie, dass der Compiler auf diese Weise wieder Ihr Freund ist. Wenn die Methode nicht "überschrieben" wird, wird beim Kompilieren ein Fehler angezeigt, anstatt derjenigen, die Sie erhalten würden, fatalError()oder Ausnahmen, die zur Laufzeit auftreten würden.


1
Imo die beste Antwort.
Apfelsaft

1
Gute Antwort, aber Ihre
Basisabstraktion

5

Ich verstehe, was Sie jetzt tun. Ich denke, Sie sollten ein Protokoll besser verwenden

protocol BaseProtocol {
    func abstractFunction()
}

Dann passen Sie sich einfach dem Protokoll an:

class SubClass : BaseProtocol {

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

Wenn Ihre Klasse auch eine Unterklasse ist, folgen die Protokolle der Oberklasse:

class SubClass: SuperClass, ProtocolOne, ProtocolTwo {}

3

Verwenden von assertSchlüsselwörtern zum Erzwingen abstrakter Methoden:

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

Wie Steve Waddicor bereits erwähnte, möchten Sie wahrscheinlich protocolstattdessen eine.


Der Vorteil abstrakter Methoden besteht darin, dass die Überprüfungen zur Kompilierungszeit durchgeführt werden.
Fabrizio Bartolomucci

Verwenden Sie nicht assert, da bei der Archivierung der Fehler "Fehlende Rückgabe in einer Funktion, von der erwartet wird, dass sie zurückgibt" angezeigt wird. Use fatalError ("Diese Methode muss von der Unterklasse überschrieben werden")
Zaporozhchenko Oleksandr

2

Ich verstehe die Frage und suchte nach der gleichen Lösung. Protokolle sind nicht dasselbe wie abstrakte Methoden.

In einem Protokoll müssen Sie angeben, dass Ihre Klasse einem solchen Protokoll entspricht. Eine abstrakte Methode bedeutet, dass Sie diese Methode überschreiben müssen.

Mit anderen Worten, Protokolle sind eine Art Option. Sie müssen die Basisklasse und das Protokoll angeben. Wenn Sie das Protokoll nicht angeben, müssen Sie solche Methoden nicht überschreiben.

Eine abstrakte Methode bedeutet, dass Sie eine Basisklasse möchten, aber Ihre oder zwei eigene Methoden implementieren müssen, was nicht dasselbe ist.

Ich brauche das gleiche Verhalten, deshalb habe ich nach einer Lösung gesucht. Ich denke, Swift fehlt eine solche Funktion.


1

Es gibt eine andere Alternative zu diesem Problem, obwohl es im Vergleich zu @ jaumards Vorschlag immer noch einen Nachteil gibt. Es erfordert eine return-Anweisung. Obwohl ich den Punkt vermisse, es zu verlangen, weil es darin besteht, eine Ausnahme direkt auszulösen:

class AbstractMethodException : NSException {

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

Und dann:

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

Was auch immer danach kommt, ist nicht erreichbar, deshalb verstehe ich nicht, warum ich die Rückkehr erzwinge.


Meinst du AbstractMethodException().raise()?
Joshcodes

Äh ... wahrscheinlich. Ich kann momentan nicht testen, aber wenn es so funktioniert, dann ja
André Fratelli

1

Ich weiß nicht, ob es nützlich sein wird, aber ich hatte ein ähnliches Problem mit der abstrahierten Methode, als ich versuchte, ein SpritKit-Spiel zu erstellen. Was ich wollte, ist eine abstrakte Tierklasse, die Methoden wie move (), run () usw. enthält, aber Sprite-Namen (und andere Funktionen) sollten von Klassenkindern bereitgestellt werden. Also habe ich so etwas gemacht (getestet für 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"]
        }
    }
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.