Classi astratte in Swift Language


141

C'è un modo per creare una classe astratta in Swift Language, o è una limitazione proprio come Objective-C? Vorrei creare una classe astratta paragonabile a quella che Java definisce una classe astratta.


Hai bisogno che l'intera classe sia astratta o solo alcuni metodi in essa contenuti? Vedi la risposta qui per singoli metodi e proprietà. stackoverflow.com/a/39038828/2435872 . In Java puoi avere classi astratte, che non hanno nessuno dei metodi astratti. Questa funzione speciale non è fornita da Swift.
jboi,

Risposte:


175

Non ci sono classi astratte in Swift (proprio come Objective-C). La tua scommessa migliore sarà quella di utilizzare un protocollo , che è come un'interfaccia Java.

Con Swift 2.0, è quindi possibile aggiungere implementazioni di metodi e implementazioni di proprietà calcolate utilizzando le estensioni di protocollo. Le tue uniche restrizioni sono che non puoi fornire variabili o costanti membri e non c'è invio dinamico .

Un esempio di questa tecnica sarebbe:

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

Si noti che ciò fornisce funzionalità di "classe astratta" anche per le strutture, ma le classi possono anche implementare lo stesso protocollo.

Si noti inoltre che ogni classe o struttura che implementa il protocollo Employee dovrà dichiarare nuovamente la proprietà annualSalary.

Soprattutto, si noti che non vi è alcuna spedizione dinamica . Quando logSalaryviene chiamato sull'istanza memorizzata come una SoftwareEngineer, chiama la versione sovrascritta del metodo. Quando logSalaryviene chiamato sull'istanza dopo che è stato Employeeeseguito il cast su un , chiama l'implementazione originale (non invia dinamicamente alla versione sovrascritta anche se l'istanza è in realtà a Software Engineer.

Per ulteriori informazioni, guarda i fantastici video del WWDC su quella funzione: Creazione di app migliori con tipi di valore in Swift


3
protocol Animal { var property : Int { get set } }. Puoi anche lasciare il set se non vuoi che la proprietà abbia un setter
drewag

3
Penso che questo video wwdc sia ancora più rilevante
Mario Zannone l'

2
@MarioZannone quel video mi ha fatto impazzire e mi ha fatto innamorare di Swift.
Scott H,

3
Se si aggiunge semplicemente func logSalary()alla dichiarazione del protocollo Employee, l'esempio viene stampato overriddenper entrambe le chiamate a logSalary(). Questo è in Swift 3.1. In questo modo ottieni i benefici del polimorfismo. Viene chiamato il metodo corretto in entrambi i casi.
Mike Taverne,

1
La regola sull'invio dinamico è questa ... se il metodo è definito solo nell'estensione, viene inviato staticamente. Se è anche definito nel protocollo che stai estendendo, viene inviato in modo dinamico. Non sono necessari runtime di Objective-C. Questo è puro comportamento rapido.
Mark A. Donohoe,

47

Nota che questa risposta è indirizzata a Swift 2.0 e versioni successive

È possibile ottenere lo stesso comportamento con protocolli ed estensioni di protocollo.

Innanzitutto, scrivi un protocollo che funge da interfaccia per tutti i metodi che devono essere implementati in tutti i tipi conformi ad esso.

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

Quindi è possibile aggiungere un comportamento predefinito a tutti i tipi conformi ad esso

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

Ora puoi creare nuovi tipi implementando Drivable.

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

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

Quindi in pratica ottieni:

  1. Compilare i controlli del tempo che garantiscono che tutti Drivableimplementinospeed
  2. È possibile implementare il comportamento predefinito per tutti i tipi conformi a Drivable( accelerate)
  3. Drivable è garantito per non essere istanziato poiché è solo un protocollo

Questo modello in realtà si comporta in modo molto più simile ai tratti, il che significa che puoi conformarti a più protocolli e assumere implementazioni predefinite di ognuno di essi, mentre con una superclasse astratta sei limitato a una semplice gerarchia di classi.


Tuttavia, non è sempre possibile estendere alcuni protocolli, ad esempio UICollectionViewDatasource. Vorrei rimuovere tutto il boilerplate e incapsularlo in un protocollo / estensione separato e riutilizzarlo da più classi. In effetti, il modello di modello sarebbe perfetto qui, ma ...
Richard Topchii,

1
Non è possibile sovrascrivere ˚accelerare˚ in ˚Auto˚. In tal caso, l'implementazione in ˚extentsion Driveable˚ viene comunque chiamata senza alcun avviso del compilatore. Molto diverso da una classe astratta di Java
Gerd Castan,

@GerdCastan True, le estensioni di protocollo non supportano l'invio dinamico.
IluTov,

15

Penso che questo sia il più vicino a Java abstracto C # abstract:

class AbstractClass {

    private init() {

    }
}

Si noti che, affinché i privatemodificatori funzionino, è necessario definire questa classe in un file Swift separato.

EDIT: Tuttavia, questo codice non consente di dichiarare un metodo astratto e quindi forzarne l'implementazione.


4
Tuttavia, ciò non obbliga una sottoclasse a sovrascrivere una funzione pur avendo un'implementazione di base di quella funzione nella classe genitore.
Matthew Quiros,

In C #, se si implementa una funzione in una classe base astratta, non si è costretti a implementarla nelle sue sottoclassi. Tuttavia, questo codice non ti consente di dichiarare un metodo astratto per forzare l'override.
Teejay,

Diciamo che la sottoclasse ConcreteClass che AbstractClass. Come si crea un'istanza di ConcreteClass?
Javier Cadiz,

2
ConcreteClass dovrebbe avere un costruttore pubblico. Probabilmente hai bisogno di un costruttore protetto in AbstractClass, a meno che non si trovino nello stesso file. Come per quello che ricordo, il modificatore di accesso protetto non esiste in Swift. Quindi la soluzione è dichiarare ConcreteClass nello stesso file.
Giovedì

13

Il modo più semplice è utilizzare una chiamata al fatalError("Not Implemented")metodo astratto (non variabile) sull'estensione del protocollo.

protocol MyInterface {
    func myMethod() -> String
}


extension MyInterface {

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

}

class MyConcreteClass: MyInterface {

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

}

MyConcreteClass().myMethod()

Questa è un'ottima risposta Non pensavo che avrebbe funzionato se avessi chiamato, (MyConcreteClass() as MyInterface).myMethod()ma funziona! La chiave è inclusa myMethodnella dichiarazione del protocollo; altrimenti la chiamata si arresta in modo anomalo.
Mike Taverne,

11

Dopo aver lottato per diverse settimane, ho finalmente capito come tradurre una classe astratta Java / PHP in 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());
    }
}

Tuttavia, penso che Apple non abbia implementato le classi astratte perché in genere utilizza invece il modello delegate + protocol. Ad esempio lo stesso modello sopra sarebbe meglio fare così:

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

Avevo bisogno di questo tipo di modello perché volevo rendere comuni alcuni metodi in UITableViewController come viewWillAppear ecc. Ti è stato utile?


1
+1 Stava progettando di seguire esattamente lo stesso approccio menzionato per primo; puntatore interessante al modello di delega.
Angad,

Inoltre, sarebbe d'aiuto se entrambi i tuoi esempi fossero sullo stesso caso d'uso. GoldenSpoonChild è un nome un po 'confuso, soprattutto perché la mamma sembra estenderlo.
Angad,

@Angad Il modello delegato è lo stesso caso d'uso, tuttavia non è una traduzione; è un modello diverso, quindi deve avere una prospettiva diversa.
Josh Woodcock,

8

Esiste un modo per simulare le classi astratte utilizzando i protocolli. Questo è un esempio:

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

Un altro modo per implementare la classe astratta è bloccare l'inizializzatore. L'ho fatto in questo modo:

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
Ciò non fornisce alcuna garanzia e / o controllo. Far saltare in aria durante il runtime è un brutto modo di far rispettare le regole. È meglio avere init come privato.
Martedì

Le classi astratte dovrebbero anche supportare i metodi astratti.
Cristik,

@Cristik Ho mostrato l'idea principale, non è una soluzione completa. In questo modo non ti piace l'80% delle risposte perché non sono abbastanza dettagliate per la tua situazione
Alexey Yarmolovich,

1
@AlexeyYarmolovich che dice che non mi piace l'80% delle risposte? :) Scherzi a parte, stavo suggerendo che il tuo esempio può essere migliorato, questo aiuterà gli altri lettori e ti aiuterà ottenendo voti.
Cristik,

0

Stavo cercando di creare una Weatherclasse astratta, ma usare i protocolli non era l'ideale poiché dovevo scrivere gli stessi initmetodi più e più volte. L'estensione del protocollo e la scrittura di un initmetodo avevano i suoi problemi, soprattutto da quando stavo usando NSObjectconformeNSCoding .

Quindi ho pensato a questo per 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...
}        

Per quanto riguarda init:

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

0

Spostare tutti i riferimenti a proprietà e metodi astratti della classe Base sull'implementazione dell'estensione del protocollo, dove Auto-vincolo sulla classe Base. Avrai accesso a tutti i metodi e le proprietà della classe Base. Inoltre, il compilatore controlla l'implementazione di metodi e proprietà astratti nel protocollo per le classi derivate

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

Con la limitazione di nessuna spedizione dinamica, potresti fare qualcosa del genere:

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()
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.