Fonctions abstraites en langage Swift

127

Je voudrais créer une fonction abstraite dans un langage rapide. C'est possible?

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

class SubClass : BaseClass {
    override func abstractFunction() {
        // Override
    }
}
kev
la source
C'est assez proche de votre autre question, mais la réponse semble un peu meilleure.
David Berry
Les questions sont similaires mais les solutions sont très différentes car une classe abstraite a des cas d'utilisation différents d'une fonction abstraite.
kev
Oui, pourquoi je n'ai pas voté pour fermer non plus, mais les réponses là-bas ne seront pas utiles au-delà de "vous ne pouvez pas" Ici vous avez la meilleure réponse disponible :)
David Berry

Réponses:

198

Il n'y a pas de concept d'abstrait dans Swift (comme Objective-C) mais vous pouvez le faire:

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

class SubClass : BaseClass {
     override func abstractFunction() {
         // Override
     } 
}
jaumard
la source
13
Vous pouvez également utiliser assert(false, "This method must be overriden by the subclass").
Erik
20
oufatalError("This method must be overridden")
nathan
5
les assertions nécessiteront une instruction return lorsqu'une méthode a un type de retour. Je pense que fatalError () est meilleur pour cette seule raison
André Fratelli
6
Il existe également preconditionFailure("Bla bla bla")dans Swift qui s'exécutera dans les versions de version et élimine également le besoin d'une déclaration de retour. EDIT: Juste a découvert que cette méthode est essentiellement égale à , fatalError()mais c'est la façon plus appropriée (documentation mieux, a présenté à Swift avec precondition(), assert()et assertionFailure(), lire ici )
Kametrixom
2
et si la fonction a un type de retour?
LoveMeow
36

Ce que vous voulez, ce n'est pas une classe de base, mais un protocole.

protocol MyProtocol {
    func abstractFunction()
}

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

Si vous ne fournissez pas abstractFunction dans votre classe, c'est une erreur.

Si vous avez toujours besoin de la classe de base pour un autre comportement, vous pouvez le faire:

class MyClass : BaseClass, MyProtocol {
    func abstractFunction() {
    }
}
Steve Waddicor
la source
23
Cela ne fonctionne pas vraiment. Si BaseClass souhaite appeler ces fonctions vides, elle devra également implémenter le protocole, puis implémenter les fonctions. De plus, la sous-classe n'est toujours pas obligée d'implémenter les fonctions au moment de la compilation, et vous n'êtes pas non plus obligé d'implémenter le protocole.
bandejapaisa
5
C'est ce que je veux dire ... BaseClass n'a rien à voir avec le protocole, et pour agir comme une classe abstraite, elle devrait avoir quelque chose à voir avec lui. Pour clarifier ce que j'ai écrit "Si BaseClass veut appeler ces fonctions vides, alors il faudrait également implémenter le protocole qui vous obligerait à implémenter les fonctions - ce qui conduit alors la sous-classe à ne pas être obligée d'implémenter les fonctions au moment de la compilation, car la BaseClass l'a déjà fait ".
bandejapaisa
4
Cela dépend vraiment de la façon dont vous définissez «abstrait». L'intérêt d'une fonction abstraite est que vous pouvez la placer sur une classe qui peut faire des choses de classe normales. Par exemple, la raison pour laquelle je veux cela est parce que j'ai besoin de définir un membre statique que vous ne semblez pas faire avec les protocoles. Il m'est donc difficile de voir que cela répond à l'intérêt d'une fonction abstraite.
Puppy du
3
Je pense que le problème avec les commentaires votés ci-dessus est que cela n'est pas conforme au principe de substitution de Liskov qui stipule que "les objets d'un programme devraient être remplaçables par des instances de leurs sous-types sans altérer l'exactitude de ce programme." On suppose que c'est un type abstrait. Ceci est particulièrement important dans le cas du modèle de méthode d'usine où la classe de base est responsable de la création et du stockage des propriétés qui proviennent de la sous-classe.
David James
2
Une classe de base abstraite et un protocole ne sont pas la même chose.
Shoerob
29

Je porte une bonne quantité de code depuis une plate-forme qui prend en charge les classes de base abstraites vers Swift et j'y suis souvent connecté. Si ce que vous voulez vraiment, c'est la fonctionnalité d'une classe de base abstraite, cela signifie que cette classe sert à la fois d'implémentation de fonctionnalité de classe partagée (sinon ce serait juste une interface / protocole) ET qu'elle définit des méthodes qui doivent être implémentées par classes dérivées.

Pour ce faire dans Swift, vous aurez besoin d'un protocole et d'une classe de base.

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

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

Notez que la classe de base implémente la ou les méthodes partagées, mais n'implémente pas le protocole (car elle n'implémente pas toutes les méthodes).

Puis dans la classe dérivée:

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

La classe dérivée hérite de sharedFunction de la classe de base, ce qui l'aide à satisfaire cette partie du protocole, et le protocole nécessite toujours que la classe dérivée implémente abstractFunction.

Le seul inconvénient réel de cette méthode est que, puisque la classe de base n'implémente pas le protocole, si vous avez une méthode de classe de base qui a besoin d'accéder à une propriété / méthode de protocole, vous devrez la remplacer dans la classe dérivée, et à partir de là, appeler la classe de base (via super) passant de selfsorte que la classe de base ait une instance du protocole avec laquelle faire son travail.

Par exemple, disons que sharedFunction avait besoin d'appeler abstractFunction. Le protocole resterait le même et les classes ressembleraient maintenant à:

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

Maintenant, la fonction sharedFunction de la classe dérivée satisfait cette partie du protocole, mais la classe dérivée est toujours capable de partager la logique de la classe de base d'une manière raisonnablement simple.

BobDickinson
la source
4
Une grande compréhension et une bonne profondeur sur le "la classe de base n'implémente pas le protocole" ... qui est un peu le point d'une classe abstraite. Je suis perplexe que cette fonctionnalité OO de base manque, mais là encore, j'ai été élevé sur Java.
Dan Rosenstark
2
Pourtant, l'implémentation ne permet pas d'implémenter de manière transparente la méthode Template Function où la méthode template appelle un grand nombre de méthodes abstraites implémentées dans les sous-classes. Dans ce cas, vous devez les écrire tous les deux comme des méthodes normales dans la super-classe, comme des méthodes dans le protocole et à nouveau comme des implémentations dans la sous-classe. Dans la pratique, vous devez écrire trois fois le même truc et ne compter que sur le contrôle de remplacement pour être sûr de ne pas faire de faute d'orthographe désastreuse! J'espère vraiment que maintenant que Swift est ouvert aux développeurs, une fonction abstraite à part entière sera introduite.
Fabrizio Bartolomucci
1
Tant de temps et de code pourraient être économisés en introduisant le mot-clé "protected".
Shoerob
23

Celui-ci semble être la manière "officielle" dont Apple gère les méthodes abstraites dans UIKit. Jetez un œil à UITableViewControllerla façon dont il fonctionne UITableViewDelegate. L' une des premières choses que vous faites, est d'ajouter une ligne: delegate = self. Eh bien, c'est exactement le truc.

1. Mettez la méthode abstraite dans un protocole
protocol AbstractMethodsForClassX {
    func abstractMethod() -> String
}
2. Écrivez votre classe de base
/// 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. Écrivez la ou les sous-classes.
/// 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!"}
}
Voici comment utiliser tout cela
let x = ClassX()
x.doSomethingWithAbstractMethod()

Vérifiez avec Playground pour voir la sortie.

Quelques remarques

  • Premièrement, beaucoup de réponses ont déjà été données. J'espère que quelqu'un trouvera tout le chemin jusqu'à celui-ci.
  • La question était en fait de trouver un modèle qui implémente:
    • Une classe appelle une méthode, qui doit être implémentée dans l'une de ses sous-classes dérivées (remplacée)
    • Dans le meilleur des cas, si la méthode n'est pas remplacée dans la sous-classe, obtenez une erreur lors de la compilation
  • Le truc à propos des méthodes abstraites est qu'elles sont un mélange d'une définition d'interface et d'une partie d'une implémentation réelle dans la classe de base. Les deux en même temps. Comme swift est très nouveau et très propre, il n'a pas une telle commodité mais des concepts "impurs" (encore).
  • Pour moi (un pauvre vieux Java), ce problème évolue de temps en temps. J'ai lu toutes les réponses dans cet article et cette fois, je pense avoir trouvé un modèle qui semble réalisable - du moins pour moi.
  • Mise à jour : il semble que les implémenteurs d'UIKit chez Apple utilisent le même modèle. UITableViewControllerimplémente UITableViewDelegatemais doit toujours être des registres en tant que délégué en définissant explicitement la delegatepropriété.
  • Tout cela est testé sur Playground de Xcode 7.3.1
jboi
la source
Peut-être pas parfait, car j'ai des problèmes à cacher l'interface de la classe aux autres classes, mais c'est assez ce dont j'avais besoin pour implémenter la méthode d'usine classique dans Swift.
Tomasz Nazarenko
Hmmm. J'aime beaucoup plus l'apparence de cette réponse, mais je ne suis pas non plus sûr qu'elle soit appropriée. Ie, j'ai un ViewController qui peut afficher des informations pour une poignée de différents types d'objets, mais le but de l'affichage de ces informations est le même, avec toutes les mêmes méthodes, mais en tirant et en rassemblant les informations différemment en fonction de l'objet . Donc, j'ai une classe de délégué parent pour ce VC et une sous-classe de ce délégué pour chaque type d'objet. J'instancie un délégué en fonction de l'objet transmis. Dans un exemple, chaque objet a des commentaires pertinents stockés.
Jake T.
J'ai donc une getCommentsméthode sur le délégué parent. Il existe une poignée de types de commentaires, et je veux ceux qui sont pertinents pour chaque objet. Donc, je veux que les sous-classes ne se compilent pas quand je le fais delegate.getComments()si je ne remplace pas cette méthode. Le VC sait simplement qu'il a un ParentDelegateobjet appelé delegate, ou BaseClassXdans votre exemple, mais BaseClassXn'a pas la méthode abstraite. J'aurais besoin que le VC sache spécifiquement qu'il utilise le SubclassedDelegate.
Jake T.
J'espère que je vous comprends bien. Sinon, cela vous dérange-t-il de poser une nouvelle question où vous pouvez ajouter du code? Je pense que vous avez juste besoin d'avoir une instruction if dans la vérification de «doSomethingWithAbstractMethod», si le délégué est défini ou non.
jboi
Il convient de mentionner que l'attribution de soi au délégué crée un cycle de rétention. Il vaut peut-être mieux le déclarer faible (ce qui est généralement une bonne idée pour les délégués)
Enricoza
7

Une façon de faire est d'utiliser une fermeture facultative définie dans la classe de base, et les enfants peuvent choisir de l'implémenter ou non.

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

class SubClass : BaseClass {
    init()
    {
        super.init()
        abstractClosure={ ..... }
    }
}
hariseldon78
la source
Ce que j'aime dans cette approche, c'est que je n'ai pas à me souvenir d'implémenter un protocole dans la classe héritière. J'ai une classe de base pour mes ViewControllers, et c'est ainsi que j'applique des fonctionnalités optionnelles spécifiques à ViewController (par exemple, l'application est devenue active, etc.) qui pourraient être appelées par la fonctionnalité de classe de base.
ProgrammierTier
6

Eh bien, je sais que je suis en retard au match et que je profite peut-être des changements qui se sont produits. Désolé pour ça.

Dans tous les cas, j'aimerais apporter ma réponse, car j'aime faire des tests et les solutions avec fatalError()est, AFAIK, non testable et celles avec des exceptions sont beaucoup plus difficiles à tester.

Je suggérerais d'utiliser une approche plus rapide . Votre objectif est de définir une abstraction qui a des détails communs, mais qui ne sont pas entièrement définis, c'est-à-dire la ou les méthodes abstraites. Utilisez un protocole qui définit toutes les méthodes attendues dans l'abstraction, à la fois celles qui sont définies et celles qui ne le sont pas. Créez ensuite une extension de protocole qui implémente les méthodes définies dans votre cas. Enfin, toute classe dérivée doit implémenter le protocole, ce qui signifie toutes les méthodes, mais celles qui font partie de l'extension de protocole ont déjà leur implémentation.

Prolonger votre exemple avec une fonction concrète:

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

Notez qu'en faisant cela, le compilateur est à nouveau votre ami. Si la méthode n'est pas "surchargée", vous obtiendrez une erreur au moment de la compilation, au lieu de celles que vous obtiendriez avec fatalError()ou des exceptions qui se produiraient au moment de l'exécution.

Jorge Ortiz
la source
1
Je suis la meilleure réponse.
Apfelsaft
1
Bonne réponse, mais votre abstraction de base n'est pas capable de stocker des propriétés, cependant
joehinkle11
5

Je comprends ce que tu fais maintenant, je pense que tu ferais mieux d'utiliser un protocole

protocol BaseProtocol {
    func abstractFunction()
}

Ensuite, vous vous conformez simplement au protocole:

class SubClass : BaseProtocol {

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

Si votre classe est également une sous-classe, les protocoles suivent la superclasse:

class SubClass: SuperClass, ProtocolOne, ProtocolTwo {}
Logan
la source
3

Utilisation de assertmots-clés pour appliquer des méthodes abstraites:

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

Cependant, comme Steve Waddicor l'a mentionné, vous en voulez probablement un à la protocolplace.

Erik
la source
L'avantage des méthodes abstraites est que les vérifications sont effectuées au moment de la compilation.
Fabrizio Bartolomucci
n'utilisez pas assert, car lors de l'archivage, vous obtiendrez l'erreur «Retour manquant dans une fonction qui devrait renvoyer». Use fatalError ("Cette méthode doit être remplacée par la sous-classe")
Zaporozhchenko Oleksandr
2

Je comprends la question et je cherchais la même solution. Les protocoles ne sont pas les mêmes que les méthodes abstraites.

Dans un protocole, vous devez spécifier que votre classe est conforme à ce protocole, une méthode abstraite signifie que vous devez remplacer cette méthode.

En d'autres termes, les protocoles sont des sortes d'options, vous devez spécifier la classe de base et le protocole, si vous ne spécifiez pas le protocole, vous n'avez pas à remplacer ces méthodes.

Une méthode abstraite signifie que vous voulez une classe de base mais que vous devez implémenter votre propre méthode ou deux, ce qui n'est pas la même chose.

J'ai besoin du même comportement, c'est pourquoi je cherchais une solution. Je suppose que Swift manque une telle fonctionnalité.

Marvin Aguero
la source
1

Il existe une autre alternative à ce problème, bien qu'il y ait toujours un inconvénient par rapport à la proposition de @ jaumard; il nécessite une déclaration de retour. Bien que je manque le point de l'exiger, car cela consiste à lancer directement une exception:

class AbstractMethodException : NSException {

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

Puis:

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

Tout ce qui vient après est inaccessible, donc je ne vois pas pourquoi forcer le retour.

André Fratelli
la source
Voulez-vous dire AbstractMethodException().raise()?
Joshcodes le
Euh ... Probablement. Je ne peux pas tester pour le moment, mais si ça marche comme ça, alors oui
André Fratelli
1

Je ne sais pas si cela va être utile, mais j'ai eu un problème similaire avec la méthode abstraite en essayant de créer le jeu SpritKit. Ce que je voulais, c'est une classe animale abstraite qui a des méthodes telles que move (), run () etc., mais les noms de sprite (et d'autres fonctionnalités) devraient être fournis par les enfants de la classe. J'ai donc fini par faire quelque chose comme ça (testé pour 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"]
        }
    }
}
interrompre
la source