web-dev-qa-db-fra.com

Fonctions abstraites en Swift Language

Je voudrais créer une fonction abstraite dans la langue Swift. Est-ce possible?

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

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

Il n'y a pas de concept 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
     } 
}
180
jaumard

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, procédez comme suit:

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

Je transfère une bonne quantité de code d'une plate-forme qui prend en charge les classes de base abstraites à Swift) et me plonge beaucoup dessus. Si ce que vous voulez vraiment est la fonctionnalité d'une classe de base abstraite, alors signifie que cette classe sert à la fois d'implémentation de la fonctionnalité de classe basée partagée (sinon, il s'agirait uniquement d'une interface/protocole) ET définit les méthodes qui doivent être implémentées par des classes dérivées.

Pour faire cela 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, l'aidant ainsi à satisfaire cette partie du protocole, et le protocole exige 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 devant accéder à une propriété/méthode de protocole, vous devrez la remplacer par celle de la classe dérivée, puis appelez-la. la classe de base (via super) en passant self de sorte que la classe de base dispose d'une instance du protocole avec laquelle effectuer son travail.

Par exemple, disons que sharedFunction devait 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 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 de manière raisonnablement simple.

28
BobDickinson

Celui-ci semble être le moyen "officiel" de savoir comment Apple gère les méthodes abstraites dans UIKit. Jetez un coup d'œil à UITableViewController et à la façon dont il fonctionne avec UITableViewDelegate Une des toutes premières choses que vous faites est d’ajouter une ligne: delegate = self. Eh bien, c'est exactement le truc.

protocol AbstractMethodsForClassX {
    func abstractMethod() -> String
}
/// 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"
    }
}
/// 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!"}
}
let x = ClassX()
x.doSomethingWithAbstractMethod()

Vérifiez auprès de 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 trouve tout le chemin à 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 (surchargée)
    • Dans le meilleur des cas, si la méthode n'est pas remplacée dans la sous-classe, obtenez une erreur pendant temps de compilation
  • Le problème des méthodes abstraites est qu’elles combinent une définition d’interface et une implémentation réelle dans la classe de base. Les deux en même temps. En tant que Swift est très nouveau et défini avec beaucoup de précision, il n’a pas cette convienience, mais des concepts "impurs" (pour le moment).
  • Pour moi (un pauvre vieux Java gars), ce problème évolue de temps en temps. J'ai lu toutes les réponses dans ce billet et cette fois, je pense avoir trouvé un motif qui a l'air faisable - du moins pour moi.
  • Mise à jour: Il semble que les développeurs UIKit à Apple utilisent le même modèle. UITableViewController implémente UITableViewDelegate mais il doit encore être enregistré. en tant que délégué en définissant explicitement la propriété delegate.
  • Tout cela est testé sur Playground of Xcode 7.3.1
21
jboi

Une façon de procéder consiste à 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={ ..... }
    }
}
7
hariseldon78

Je comprends ce que vous faites maintenant, je pense que vous feriez mieux d'utiliser un protocole

protocol BaseProtocol {
    func abstractFunction()
}

Ensuite, vous venez de vous conformer au protocole:

class SubClass : BaseProtocol {

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

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

class SubClass: SuperClass, ProtocolOne, ProtocolTwo {}
5
Logan

Eh bien, je sais que je suis en retard au jeu et que je profite peut-être des changements survenus. Désolé pour ça.

Quoi qu'il en soit, j'aimerais apporter ma réponse, car j'aime bien tester et que les solutions avec fatalError() ne sont pas testables et que celles qui contiennent 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 présente des détails communs, mais qui n’est pas complètement définie, 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.

Étendre 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 "remplacée", vous obtiendrez une erreur lors de la compilation, à la place de celles que vous obtiendriez avec fatalError() ou d'exceptions qui se produiraient au moment de l'exécution.

4
Jorge Ortiz

Utilisation du mot clé assert 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

Toutefois, comme Steve Waddicor a mentionné, vous souhaiterez probablement un protocol à la place.

3
Erik

Je comprends la question et 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 en quelque sorte des 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é.

2
Marvin Aguero

Il existe une autre solution à ce problème, mais il existe 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.

1
André Fratelli

Je ne sais pas si ça 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 dotée de méthodes telles que move (), run (), etc., mais les noms de sprites (et d'autres fonctionnalités) doivent être fournis par les enfants de la classe. J'ai donc fini par faire quelque chose comme ceci (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"]
        }
    }
}
1
interrupt