Cours abstraits en langage Swift


141

Existe-t-il un moyen de créer une classe abstraite dans le langage Swift, ou est-ce une limitation tout comme Objective-C? Je voudrais créer une classe abstraite comparable à ce que Java définit comme une classe abstraite.


Avez-vous besoin que la classe complète soit abstraite ou juste quelques méthodes? Voir la réponse ici pour les méthodes et propriétés uniques. stackoverflow.com/a/39038828/2435872 . En Java, vous pouvez avoir des classes abstraites, qui n'ont aucune des méthodes abstraites. Cette fonction spéciale n'est pas fournie par Swift.
jboi

Réponses:


175

Il n'y a pas de classes abstraites dans Swift (tout comme Objective-C). Votre meilleur pari sera d'utiliser un protocole , qui est comme une interface Java.

Avec Swift 2.0, vous pouvez ensuite ajouter des implémentations de méthodes et des implémentations de propriétés calculées à l'aide d'extensions de protocole. Vos seules restrictions sont que vous ne pouvez pas fournir de variables ou de constantes membres et qu'il n'y a pas de répartition dynamique .

Un exemple de cette technique serait:

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

Notez que cela fournit des fonctionnalités comme des "classes abstraites" même pour les structures, mais les classes peuvent également implémenter le même protocole.

Notez également que chaque classe ou structure qui implémente le protocole Employee devra déclarer à nouveau la propriété annualSalary.

Surtout, notez qu'il n'y a pas de répartition dynamique . When logSalaryest appelé sur l'instance stockée en tant que, SoftwareEngineeril appelle la version remplacée de la méthode. Lorsqu'elle logSalaryest appelée sur l'instance après avoir été convertie en an Employee, elle appelle l'implémentation d'origine (elle ne distribue pas dynamiquement à la version remplacée même si l'instance est en fait un Software Engineer.

Pour plus d'informations, regardez la superbe vidéo WWDC sur cette fonctionnalité: Créer de meilleures applications avec des types de valeur dans Swift


3
protocol Animal { var property : Int { get set } }. Vous pouvez également
omettre

3
Je pense que cette vidéo wwdc est encore plus pertinente
Mario Zannone

2
@MarioZannone, cette vidéo m'a époustouflé et m'a fait tomber amoureux de Swift.
Scott H le

3
Si vous ajoutez simplement func logSalary()à la déclaration de protocole Employee, l'exemple s'imprimeoverridden pour les deux appels à logSalary(). C'est dans Swift 3.1. Ainsi, vous bénéficiez des avantages du polymorphisme. La méthode correcte est appelée dans les deux cas.
Mike Taverne

1
La règle concernant la répartition dynamique est la suivante ... si la méthode est définie uniquement dans l'extension, elle est répartie de manière statique. S'il est également défini dans le protocole que vous étendez, il est distribué dynamiquement. Pas besoin de runtimes Objective-C. C'est un pur comportement Swift.
Mark A. Donohoe

47

Notez que cette réponse est destinée à Swift 2.0 et supérieur

Vous pouvez obtenir le même comportement avec les protocoles et les extensions de protocole.

Tout d'abord, vous écrivez un protocole qui agit comme une interface pour toutes les méthodes qui doivent être implémentées dans tous les types qui s'y conforment.

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

Ensuite, vous pouvez ajouter un comportement par défaut à tous les types qui s'y conforment

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

Vous pouvez maintenant créer de nouveaux types en implémentant Drivable.

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

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

Donc, fondamentalement, vous obtenez:

  1. Compilez des contrôles de temps qui garantissent que tous Drivableles implémententspeed
  2. Vous pouvez implémenter le comportement par défaut pour tous les types conformes à Drivable( accelerate)
  3. Drivable est garanti de ne pas être instancié car il ne s'agit que d'un protocole

Ce modèle se comporte en fait beaucoup plus comme des traits, ce qui signifie que vous pouvez vous conformer à plusieurs protocoles et prendre des implémentations par défaut de l'un d'entre eux, alors qu'avec une superclasse abstraite, vous êtes limité à une simple hiérarchie de classes.


Pourtant, il n'est pas toujours possible d'étendre certains protocoles, par exemple UICollectionViewDatasource,. Je voudrais supprimer tout le passe-partout et l'encapsuler dans un protocole / extension séparé, puis le réutiliser par plusieurs classes. En fait, le modèle de modèle serait parfait ici, mais ...
Richard Topchii

1
Vous ne pouvez pas écraser «Accélérer» dans «Voiture». Si vous le faites, l'implémentation dans ˚extentsion Driveable˚ est toujours appelée sans aucun avertissement du compilateur. Très différent d'une classe abstraite Java
Gerd Castan

@GerdCastan True, les extensions de protocole ne prennent pas en charge la répartition dynamique.
IluTov

15

Je pense que c'est le plus proche de Java abstractou C # abstract:

class AbstractClass {

    private init() {

    }
}

Notez que, pour que les privatemodificateurs fonctionnent, vous devez définir cette classe dans un fichier Swift séparé.

EDIT: Pourtant, ce code ne permet pas de déclarer une méthode abstraite et donc de forcer son implémentation.


4
Néanmoins, cela ne force pas une sous-classe à remplacer une fonction tout en ayant également une implémentation de base de cette fonction dans la classe parente.
Matthew Quiros

En C #, si vous implémentez une fonction dans une classe de base abstraite, vous n'êtes pas obligé de l'implémenter dans ses sous-classes. Pourtant, ce code ne vous permet pas de déclarer une méthode abstraite afin de forcer le remplacement.
Teejay

Disons que ConcreteClass sous-classe que AbstractClass. Comment instancier ConcreteClass?
Javier Cadiz

2
ConcreteClass doit avoir un constructeur public. Vous avez probablement besoin d'un constructeur protégé dans AbstractClass, sauf s'il se trouve dans le même fichier. D'après ce dont je me souviens, le modificateur d'accès protégé n'existe pas dans Swift. La solution est donc de déclarer ConcreteClass dans le même fichier.
Teejay

13

Le moyen le plus simple consiste à utiliser un appel à fatalError("Not Implemented")dans la méthode abstraite (non variable) sur l'extension de protocole.

protocol MyInterface {
    func myMethod() -> String
}


extension MyInterface {

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

}

class MyConcreteClass: MyInterface {

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

}

MyConcreteClass().myMethod()

C'est une excellente réponse. Je ne pensais pas que cela fonctionnerait si vous appeliez, (MyConcreteClass() as MyInterface).myMethod()mais c'est le cas! La clé est d'inclure myMethoddans la déclaration de protocole; sinon, l'appel se bloque.
Mike Taverne

11

Après avoir lutté pendant plusieurs semaines, j'ai finalement réalisé comment traduire une classe abstraite Java / PHP en 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());
    }
}

Cependant, je pense qu'Apple n'a pas implémenté de classes abstraites car il utilise généralement le modèle de protocole délégué + à la place. Par exemple, le même modèle ci-dessus serait mieux fait comme ceci:

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

J'avais besoin de ce type de modèle parce que je voulais vulgariser certaines méthodes dans UITableViewController telles que viewWillAppear, etc. Cela vous a-t-il été utile?


1
+1 Je prévoyais de faire exactement la même approche que vous avez mentionnée en premier; pointeur intéressant sur le modèle de délégation.
Angad

En outre, cela aiderait si vos deux exemples étaient sur le même cas d'utilisation. GoldenSpoonChild est un nom légèrement déroutant, d'autant plus que Mère semble l'élargir.
Angad

@Angad Le modèle de délégué est le même cas d'utilisation, mais ce n'est pas une traduction; c'est un modèle différent, il doit donc adopter une perspective différente.
Josh Woodcock

8

Il existe un moyen de simuler des classes abstraites à l'aide de protocoles. Ceci est un exemple:

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

1

Une autre façon d'implémenter une classe abstraite consiste à bloquer l'initialiseur. Je l'ai fait de cette façon:

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

4
Cela ne fournit aucune garantie et / ou contrôle. Faire exploser pendant l'exécution est une mauvaise façon d'appliquer les règles. Il vaut mieux que l'init soit privé.
Морт

Les classes abstraites devraient également prendre en charge les méthodes abstraites.
Cristik

@Cristik J'ai montré l'idée principale, ce n'est pas une solution complète. De cette façon, vous pouvez ne pas aimer 80% des réponses car elles ne sont pas assez détaillées pour votre situation
Alexey Yarmolovich

1
@AlexeyYarmolovich qui dit que je n'aime pas 80% des réponses? :) Blague à part, je suggérais que votre exemple peut être amélioré, cela aidera les autres lecteurs et vous aidera en obtenant des votes positifs.
Cristik

0

J'essayais de créer une Weatherclasse abstraite, mais utiliser des protocoles n'était pas idéal car je devais écrire les mêmes initméthodes encore et encore. Étendre le protocole et écrire une initméthode posait ses problèmes, d'autant plus que j'utilisais NSObjectconforming to NSCoding.

J'ai donc proposé ceci pour la NSCodingconformité:

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

Quant à init:

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

0

Déplacez toutes les références aux propriétés abstraites et aux méthodes de la classe de base vers l'implémentation de l'extension de protocole, où Self contrainte vers la classe de base. Vous aurez accès à toutes les méthodes et propriétés de la classe de base. En outre, le compilateur vérifie l'implémentation des méthodes abstraites et des propriétés dans le protocole pour les classes dérivées

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

0

Avec la limitation d'aucune répartition dynamique, vous pouvez faire quelque chose comme ceci:

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()
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.