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.