Funzioni astratte in Swift Language


127

Vorrei creare una funzione astratta in un linguaggio rapido. È possibile?

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

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

Questo è abbastanza vicino all'altra tua domanda, ma la risposta qui sembra un po 'migliore.
David Berry,

Le domande sono simili ma le soluzioni sono molto diverse perché una classe astratta ha casi d'uso diversi rispetto a una funzione astratta.
kev

Sì, perché non ho votato neanche per chiudere, ma le risposte non saranno utili oltre a "non puoi". Qui hai la migliore risposta disponibile :)
David Berry

Risposte:


198

Non esiste un concetto di abstract in Swift (come Objective-C) ma puoi farlo:

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

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

13
In alternativa puoi usare assert(false, "This method must be overriden by the subclass").
Erik,

20
oppurefatalError("This method must be overridden")
nathan,

5
le asserzioni richiederanno una dichiarazione di ritorno quando un metodo ha un tipo di ritorno. Penso che fatalError () sia migliore per questo motivo
André Fratelli il

6
Inoltre c'è preconditionFailure("Bla bla bla")in Swift che verrà eseguito nelle build di rilascio ed elimina anche la necessità di una dichiarazione di ritorno. EDIT: appena scoperto che questo metodo è fondamentalmente uguale fatalError()ma è il modo più corretto (Migliore documentazione, introdotto nel Swift insieme precondition(), assert()e assertionFailure(), leggere qui )
Kametrixom

2
cosa succede se la funzione ha un tipo restituito?
LoveMeow,

36

Quello che vuoi non è una classe base, ma un protocollo.

protocol MyProtocol {
    func abstractFunction()
}

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

Se non fornisci abstractFunction nella tua classe, è un errore.

Se hai ancora bisogno della base per altri comportamenti, puoi farlo:

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

23
Questo non funziona del tutto. Se BaseClass vuole chiamare quelle funzioni vuote, allora dovrebbe anche implementare il protocollo e quindi implementare le funzioni. Inoltre, la sottoclasse non è ancora obbligata a implementare le funzioni in fase di compilazione e non è neppure obbligato a implementare il protocollo.
bandejapaisa,

5
Questo è il mio punto ... BaseClass non ha nulla a che fare con il protocollo, e per comportarsi come una classe astratta dovrebbe avere qualcosa a che fare con esso. Per chiarire cosa ho scritto "Se BaseClass vuole chiamare quelle funzioni vuote, allora dovrebbe anche implementare il protocollo che ti costringerebbe ad implementare le funzioni - il che porta quindi alla sottoclasse a non essere costretta ad implementare le funzioni in fase di compilazione, perché BaseClass l'ha già fatto ".
bandejapaisa,

4
Dipende molto da come definisci "astratto". Il punto centrale di una funzione astratta è che puoi metterlo in una classe che può fare cose normali in classe. Ad esempio, il motivo per cui lo voglio è perché ho bisogno di definire un membro statico che non riesci a fare con i protocolli. Quindi è difficile per me vedere che questo soddisfa il punto utile di una funzione astratta.
Puppy,

3
Penso che la preoccupazione con i commenti votati sopra sia che questo non è conforme al principio di sostituzione di Liskov che afferma che "gli oggetti in un programma dovrebbero essere sostituibili con le istanze dei loro sottotipi senza alterare la correttezza di quel programma". Si presume che questo sia un tipo astratto. Ciò è particolarmente importante nel caso del modello di Metodo di fabbrica in cui la classe base è responsabile della creazione e della memorizzazione delle proprietà originate dalla sottoclasse.
David James,

2
Una classe base astratta e un protocollo non sono la stessa cosa.
Shoerob

29

Porto una discreta quantità di codice da una piattaforma che supporta classi base astratte su Swift e corro molto. Se quello che vuoi veramente è la funzionalità di una classe base astratta, allora ciò significa che questa classe serve sia come implementazione della funzionalità di classe basata condivisa (altrimenti sarebbe solo un'interfaccia / protocollo) E definisce i metodi che devono essere implementati da classi derivate.

Per farlo in Swift, avrai bisogno di un protocollo e di una classe base.

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

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

Si noti che la classe base implementa i metodi condivisi, ma non implementa il protocollo (poiché non implementa tutti i metodi).

Quindi nella classe derivata:

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

La classe derivata eredita sharedFunction dalla classe base, aiutandola a soddisfare quella parte del protocollo e il protocollo richiede ancora la classe derivata per implementare abstractFunction.

L'unico vero svantaggio di questo metodo è che, poiché la classe base non implementa il protocollo, se si dispone di un metodo della classe base che richiede l'accesso a una proprietà / metodo del protocollo, è necessario sovrascriverlo nella classe derivata e da lì chiamare la classe base (tramite super) passa in selfmodo che la classe base abbia un'istanza del protocollo con cui svolgere il proprio lavoro.

Ad esempio, supponiamo che sharedFunction debba chiamare abstractFunction. Il protocollo rimarrebbe lo stesso e le classi ora sembrerebbero:

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

Ora sharedFunction dalla classe derivata sta soddisfacendo quella parte del protocollo, ma la classe derivata è ancora in grado di condividere la logica della classe base in modo ragionevolmente semplice.


4
Grande comprensione e buona conoscenza della "classe di base non implementa il protocollo" ... che è una specie di punto di una classe astratta. Sono sconcertato dal fatto che questa funzionalità OO di base manchi, ma poi sono stato cresciuto su Java.
Dan Rosenstark,

2
Tuttavia, l'implementazione non consente di implementare senza soluzione di continuità il metodo Template Function in cui il metodo template chiama un gran numero di metodi astratti implementati nelle sottoclassi. In questo caso dovresti scriverli entrambi come metodi normali nella superclasse, come metodi nel protocollo e di nuovo come implementazioni nella sottoclasse. In pratica devi scrivere tre volte le stesse cose e fare affidamento solo sul controllo di sostituzione per essere sicuro di non fare errori di ortografia disastrosi! Spero davvero ora che Swift sia aperto agli sviluppatori, verrà introdotta la funzione astratta a tutti gli effetti.
Fabrizio Bartolomucci il

1
Così tanto tempo e il codice potrebbe essere salvato introducendo la parola chiave "protetta".
Shoerob

23

Questo sembra essere il modo "ufficiale" di come Apple gestisce i metodi astratti in UIKit. Dai un'occhiata UITableViewControllere come funziona UITableViewDelegate. Una delle primissime cose che fai è aggiungere una riga:delegate = self . Bene, questo è esattamente il trucco.

1. Inserire il metodo astratto in un protocollo
protocol AbstractMethodsForClassX {
    func abstractMethod() -> String
}
2. Scrivi la tua classe 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. Scrivere le sottoclassi.
/// 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!"}
}
Ecco come usare tutto ciò
let x = ClassX()
x.doSomethingWithAbstractMethod()

Verificare con Playground per vedere l'output.

Alcune osservazioni

  • Innanzitutto, sono state già fornite molte risposte. Spero che qualcuno trovi fino a questo.
  • In realtà la domanda era: trovare un modello che implementasse:
    • Una classe chiama un metodo, che deve essere implementato in una delle sue sottoclassi derivate (sovrascritto)
    • Nel migliore dei casi, se il metodo non viene sovrascritto nella sottoclasse, viene visualizzato un errore durante il tempo di compilazione
  • La cosa sui metodi astratti è che sono una miscela di una definizione di interfaccia e parte di un'implementazione effettiva nella classe base. Entrambi allo stesso tempo. Dal momento che rapido è molto nuovo e definito in modo molto pulito, non ha tale convenienza ma concetti "sporchi" (ancora).
  • Per me (un povero vecchio ragazzo di Java), questo problema si evolve di volta in volta. Ho letto tutte le risposte in questo post e questa volta penso di aver trovato uno schema che sembra fattibile, almeno per me.
  • Aggiornamento : sembra che gli implementatori UIKit di Apple utilizzino lo stesso schema. UITableViewControllerimplementa UITableViewDelegatema deve comunque essere registrato come delegato impostando esplicitamente la delegateproprietà.
  • Tutto questo è testato su Playground di Xcode 7.3.1

Forse non è perfetto, perché ho problemi a nascondere l'interfaccia della classe da altre classi, ma questo è sufficiente per implementare il metodo Factory classico in Swift.
Tomasz Nazarenko,

Hmmm. Mi piace molto di più l'aspetto di questa risposta, ma non sono sicuro che sia adatto. Cioè, ho un ViewController che può visualizzare informazioni per una manciata di diversi tipi di oggetti, ma lo scopo di visualizzare tali informazioni è lo stesso, con tutti gli stessi metodi, ma tirare e mettere insieme le informazioni in modo diverso in base all'oggetto . Quindi, ho una classe delegata padre per questo VC e una sottoclasse di quel delegato per ogni tipo di oggetto. Istanza un delegato in base all'oggetto passato. In un esempio, ogni oggetto ha alcuni commenti rilevanti memorizzati.
Jake T.

Quindi ho un getCommentsmetodo sul delegato principale. C'è una manciata di tipi di commenti e voglio quelli pertinenti a ciascun oggetto. Quindi, voglio che le sottoclassi non vengano compilate quando lo faccio, delegate.getComments()se non sovrascrivo quel metodo. Il VC sa solo che ha un ParentDelegateoggetto chiamato delegate, o BaseClassXnel tuo esempio, ma BaseClassXnon ha il metodo sottratto. Avrei bisogno che il VC sapesse specificamente che sta usando SubclassedDelegate.
Jake T.

Spero di averti capito bene. In caso contrario, ti dispiace fare una nuova domanda in cui è possibile aggiungere del codice? Penso che devi solo avere un'istruzione if nel controllo "doSomethingWithAbstractMethod", se il delegato è impostato o meno.
jboi,

Vale la pena ricordare che l'assegnazione di sé al delegato crea un ciclo di mantenimento. Forse è meglio dichiararlo debole (che è generalmente una buona idea per i delegati)
Enricoza,

7

Un modo per farlo è usare una chiusura opzionale definita nella classe base, e i bambini possono scegliere di implementarla o meno.

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

class SubClass : BaseClass {
    init()
    {
        super.init()
        abstractClosure={ ..... }
    }
}

Quello che mi piace di questo approccio è che non devo ricordare di implementare un protocollo nella classe ereditaria. Ho una classe di base per i miei ViewController e questo è il modo in cui imposto le funzionalità opzionali specifiche di ViewController (ad esempio l'app è diventata attiva, ecc.) Che potrebbero essere invocate dalla funzionalità della classe di base.
ProgrammierTier,

6

Bene, so che sono in ritardo al gioco e che potrei approfittare dei cambiamenti che sono avvenuti. Mi dispiace per quello.

In ogni caso, vorrei contribuire con la mia risposta, perché adoro fare test e le soluzioni con fatalError()AFAIK non sono testabili e quelle con eccezioni sono molto più difficili da testare.

Suggerirei di utilizzare un approccio più rapido . Il tuo obiettivo è definire un'astrazione che abbia alcuni dettagli comuni, ma che non sia completamente definita, ovvero i metodi astratti. Utilizzare un protocollo che definisce tutti i metodi previsti nell'astrazione, sia quelli definiti, sia quelli che non lo sono. Quindi crea un'estensione del protocollo che implementa i metodi definiti nel tuo caso. Infine, qualsiasi classe derivata deve implementare il protocollo, il che significa che tutti i metodi, ma quelli che fanno parte dell'estensione del protocollo hanno già la loro implementazione.

Estendere il tuo esempio con una funzione concreta:

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

Notare che facendo questo, il compilatore è di nuovo tuo amico. Se il metodo non è "sovrascritto", al momento della compilazione verrà visualizzato un errore, anziché quelli che si otterrebbero fatalError()o le eccezioni che potrebbero verificarsi in fase di esecuzione.


1
Imo la migliore risposta.
Apfelsaft

1
Buona risposta, ma la tua astrazione di base non è in grado di memorizzare le proprietà, comunque
joehinkle11

5

Capisco cosa stai facendo ora, penso che staresti meglio usando un protocollo

protocol BaseProtocol {
    func abstractFunction()
}

Quindi, ti devi solo conformare al protocollo:

class SubClass : BaseProtocol {

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

Se la tua classe è anche una sottoclasse, i protocolli seguono la Superclass:

class SubClass: SuperClass, ProtocolOne, ProtocolTwo {}

3

Utilizzo della assertparola chiave per applicare metodi astratti:

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

Tuttavia, come ha detto Steve Waddicor , probabilmente vorrai un protocolinvece.


Il vantaggio di metodi astratti è che i controlli vengono eseguiti in fase di compilazione.
Fabrizio Bartolomucci il

non usare assert, perché durante l'archiviazione otterrai l'errore "Ritorno mancante in una funzione che dovrebbe restituire". Usa fatalError ("Questo metodo deve essere sostituito dalla sottoclasse")
Zaporozhchenko Oleksandr

2

Capisco la domanda e cercavo la stessa soluzione. I protocolli non sono gli stessi dei metodi astratti.

In un protocollo devi specificare che la tua classe è conforme a tale protocollo, un metodo astratto significa che devi sovrascrivere tale metodo.

In altre parole, i protocolli sono di tipo opzionale, è necessario specificare la classe di base e il protocollo, se non si specifica il protocollo, non è necessario sovrascrivere tali metodi.

Un metodo astratto significa che vuoi una classe base ma devi implementare il tuo metodo o due, che non è lo stesso.

Ho bisogno dello stesso comportamento, ecco perché stavo cercando una soluzione. Immagino che a Swift manchi tale caratteristica.


1

C'è un'altra alternativa a questo problema, anche se c'è ancora un aspetto negativo rispetto alla proposta di @ jaumard; richiede una dichiarazione di ritorno. Anche se mi manca il punto di richiederlo, perché consiste nel lanciare direttamente un'eccezione:

class AbstractMethodException : NSException {

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

E poi:

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

Qualunque cosa venga dopo è irraggiungibile, quindi non vedo perché forzare il ritorno.


Vuoi dire AbstractMethodException().raise()?
Joshcodes,

Err ... Probabilmente. Non posso testare adesso, ma se funziona così, allora sì
André Fratelli

1

Non so che sarà utile, ma ho avuto un problema simile con il metodo astratto durante il tentativo di compilare il gioco SpritKit. Quello che volevo è una classe Animal astratta che abbia metodi come move (), run () ecc., Ma i nomi degli sprite (e altre funzionalità) dovrebbero essere forniti dai figli della classe. Quindi ho finito per fare qualcosa del genere (testato per 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"]
        }
    }
}
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.