Funciones abstractas en lenguaje Swift

127

Me gustaría crear una función abstracta en lenguaje rápido. ¿Es posible?

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

class SubClass : BaseClass {
    override func abstractFunction() {
        // Override
    }
}
kev
fuente
Esto está bastante cerca de su otra pregunta, pero la respuesta aquí parece un poco mejor.
David Berry
Las preguntas son similares, pero las soluciones son muy diferentes porque una clase abstracta tiene diferentes casos de uso que una función abstracta.
kev
Sí, por qué no voté para cerrar tampoco, pero las respuestas allí no serán útiles más allá de "no puedes" Aquí tienes la mejor respuesta disponible :)
David Berry

Respuestas:

198

No hay concepto de resumen en Swift (como Objective-C) pero puede hacer esto:

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

class SubClass : BaseClass {
     override func abstractFunction() {
         // Override
     } 
}
jaumard
fuente
13
Alternativamente puedes usar assert(false, "This method must be overriden by the subclass").
Erik
20
ofatalError("This method must be overridden")
nathan
55
las aserciones requerirán una declaración de devolución cuando un método tenga un tipo de devolución. Creo que fatalError () es mejor por esta única razón
André Fratelli el
66
También hay preconditionFailure("Bla bla bla")en Swift que se ejecutará en versiones de lanzamiento y también elimina la necesidad de una declaración de devolución. EDITAR: Acabo de descubrir que este método básicamente es igual fatalError()pero es la forma más adecuada (Mejor documentación, presentada en Swift junto con precondition(), assert()y assertionFailure(), lea aquí )
Kametrixom
2
¿Qué pasa si la función tiene un tipo de retorno?
LoveMeow
36

Lo que quieres no es una clase base, sino un protocolo.

protocol MyProtocol {
    func abstractFunction()
}

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

Si no proporciona abstractFunction en su clase, es un error.

Si aún necesita la clase base para otro comportamiento, puede hacer esto:

class MyClass : BaseClass, MyProtocol {
    func abstractFunction() {
    }
}
Steve Waddicor
fuente
23
Esto no funciona del todo. Si BaseClass quiere llamar a esas funciones vacías, entonces también tendría que implementar el protocolo y luego implementar las funciones. Además, la subclase todavía no está obligada a implementar las funciones en tiempo de compilación, y tampoco está obligado a implementar el protocolo.
bandejapaisa
55
Ese es mi punto ... BaseClass no tiene nada que ver con el protocolo, y para actuar como una clase abstracta, debería tener algo que ver con él. Para aclarar lo que escribí "Si BaseClass quiere llamar a esas funciones vacías, entonces también tendría que implementar el protocolo que lo obligaría a implementar las funciones, lo que lleva a que la Subclase no se vea obligada a implementar las funciones en tiempo de compilación, porque BaseClass ya lo ha hecho ".
bandejapaisa
44
Eso realmente depende de cómo se defina "abstracto". El objetivo de una función abstracta es que puedes ponerla en una clase que pueda hacer cosas de clase normales. Por ejemplo, la razón por la que quiero esto es porque necesito definir un miembro estático que parece que no puede hacer con los protocolos. Entonces es difícil para mí ver que esto cumple con el punto útil de una función abstracta.
Puppy
3
Creo que la preocupación con los comentarios votados anteriormente es que esto no se ajusta al principio de sustitución de Liskov que establece que "los objetos en un programa deben ser reemplazables con instancias de sus subtipos sin alterar la corrección de ese programa". Se supone que este es un tipo abstracto. Esto es particularmente importante en el caso del patrón Método de fábrica donde la clase base es responsable de crear y almacenar propiedades que se originan en la subclase.
David James
2
Una clase base abstracta y un protocolo no son lo mismo.
Shoerob
29

Porto una buena cantidad de código desde una plataforma que admite clases base abstractas a Swift y me encuentro mucho con esto. Si lo que realmente desea es la funcionalidad de una clase base abstracta, eso significa que esta clase sirve tanto como una implementación de la funcionalidad de clase basada compartida (de lo contrario, solo sería una interfaz / protocolo) Y define métodos que deben implementarse mediante clases derivadas

Para hacer eso en Swift, necesitará un protocolo y una clase base.

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

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

Tenga en cuenta que la clase base implementa los métodos compartidos, pero no implementa el protocolo (ya que no implementa todos los métodos).

Luego en la clase derivada:

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

La clase derivada hereda sharedFunction de la clase base, lo que le ayuda a satisfacer esa parte del protocolo, y el protocolo aún requiere que la clase derivada implemente abstractFunction.

El único inconveniente real de este método es que, dado que la clase base no implementa el protocolo, si tiene un método de clase base que necesita acceso a una propiedad / método de protocolo, tendrá que anularlo en la clase derivada, y desde allí llamar la clase base (a través de super) pasando selfpara que la clase base tenga una instancia del protocolo con el que hacer su trabajo.

Por ejemplo, digamos que sharedFunction necesitaba llamar a abstractFunction. El protocolo permanecería igual y las clases ahora se verían así:

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

Ahora, la función compartida de la clase derivada satisface esa parte del protocolo, pero la clase derivada aún puede compartir la lógica de la clase base de una manera razonablemente sencilla.

BobDickinson
fuente
44
Gran comprensión y buena profundidad sobre "la clase base no implementa el protocolo" ... que es una especie de punto de una clase abstracta. Estoy desconcertado de que falta esta característica básica de OO, pero de nuevo, me crié en Java.
Dan Rosenstark
2
Sin embargo, la implementación no permite implementar sin problemas el método de la función de plantilla donde el método de plantilla llama a un gran número de métodos abstractos implementados en las subclases. En este caso, debe escribirlos como métodos normales en la superclase, como métodos en el protocolo y nuevamente como implementaciones en la subclase. ¡En la práctica, tiene que escribir tres veces lo mismo y solo confiar en la verificación de anulación para asegurarse de no cometer errores ortográficos desastrosos! Realmente espero que ahora Swift esté abierto a los desarrolladores, se presente una función abstracta completa.
Fabrizio Bartolomucci
1
Tanto tiempo, y el código podría salvarse introduciendo la palabra clave "protegida".
Shoerob
23

Esta parece ser la forma "oficial" de cómo Apple maneja métodos abstractos en UIKit. Eche un vistazo UITableViewControllery la forma en que funciona UITableViewDelegate. Una de las primeras cosas que hacer, es añadir una línea: delegate = self. Bueno, ese es exactamente el truco.

1. Ponga el método abstracto en un protocolo
protocol AbstractMethodsForClassX {
    func abstractMethod() -> String
}
2. Escribe tu clase 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. Escriba la subclase (s).
/// 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!"}
}
Aquí está, cómo usar todo eso
let x = ClassX()
x.doSomethingWithAbstractMethod()

Consulte con Playground para ver la salida.

Algunas observaciones

  • Primero, ya se dieron muchas respuestas. Espero que alguien encuentre todo el camino hasta este.
  • La pregunta en realidad era encontrar un patrón que implemente:
    • Una clase llama a un método que debe implementarse en una de sus subclases derivadas (anulada)
    • En el mejor de los casos, si el método no se anula en la subclase, obtenga un error durante el tiempo de compilación
  • Lo que pasa con los métodos abstractos es que son una mezcla de una definición de interfaz y parte de una implementación real en la clase base. Los dos al mismo tiempo. Como swift es muy nuevo y está definido de manera muy limpia, no tiene esa conveniencia sino conceptos "impuros" (todavía).
  • Para mí (un pobre chico de Java), este problema evoluciona de vez en cuando. He leído todas las respuestas en esta publicación y esta vez creo que encontré un patrón que parece factible, al menos para mí.
  • Actualización : Parece que los implementadores de UIKit en Apple usan el mismo patrón. UITableViewControllerimplementa UITableViewDelegatepero aún necesita ser registros como delegado estableciendo explícitamente la delegatepropiedad.
  • Todo esto se prueba en Playground de Xcode 7.3.1
jboi
fuente
Tal vez no sea perfecto, porque tengo problemas para ocultar la interfaz de la clase de otras clases, pero esto es suficiente para implementar el Método de fábrica clásico en Swift.
Tomasz Nazarenko
Hmmm Me gusta mucho más el aspecto de esta respuesta, pero tampoco estoy seguro de que sea adecuada. Es decir, tengo un ViewController que puede mostrar información para un puñado de diferentes tipos de objetos, pero el propósito de mostrar esa información es el mismo, con todos los mismos métodos, pero obteniendo y juntando la información de manera diferente en función del objeto . Entonces, tengo una clase de delegado principal para este VC y una subclase de ese delegado para cada tipo de objeto. Ejecuté un delegado basado en el objeto pasado. En un ejemplo, cada objeto tiene algunos comentarios relevantes almacenados.
Jake T.
Entonces tengo un getCommentsmétodo en el delegado principal. Hay un puñado de tipos de comentarios, y quiero los relevantes para cada objeto. Entonces, quiero que las subclases no se compilen cuando lo hago delegate.getComments()si no anulo ese método. El VC solo sabe que tiene un ParentDelegateobjeto llamado delegate, o BaseClassXen su ejemplo, pero BaseClassXno tiene el método abstracto. Necesitaría el VC para saber específicamente que está usando el SubclassedDelegate.
Jake T.
Espero haberte entendido bien. Si no, ¿te importaría hacer una nueva pregunta donde puedes agregar algún código? Creo que solo necesita tener una instrucción if en la comprobación 'doSomethingWithAbstractMethod', si el delegado está configurado o no.
jboi
Vale la pena mencionar que asignarse uno mismo al delegado crea un ciclo de retención. Tal vez sea mejor declararlo débil (lo que suele ser una buena idea para los delegados)
Enricoza
7

Una forma de hacerlo es usar un cierre opcional definido en la clase base, y los niños pueden elegir implementarlo o no.

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

class SubClass : BaseClass {
    init()
    {
        super.init()
        abstractClosure={ ..... }
    }
}
hariseldon78
fuente
Lo que me gusta de este enfoque es que no tengo que recordar implementar un protocolo en la clase heredada. Tengo una clase base para mis ViewControllers, y así es como hago cumplir la funcionalidad opcional específica de ViewController (por ejemplo, la aplicación se activó, etc.) que podría ser invocada por la funcionalidad de la clase base.
ProgrammierTier
6

Bueno, sé que llego tarde al juego y que podría estar aprovechando los cambios que han sucedido. Lo siento por eso.

En cualquier caso, me gustaría contribuir con mi respuesta, porque me encanta hacer las pruebas y las soluciones con fatalError(), AFAIK, no son verificables y las que tienen excepciones son mucho más difíciles de probar.

Sugeriría utilizar un enfoque más rápido . Su objetivo es definir una abstracción que tenga algunos detalles comunes, pero que no esté completamente definida, es decir, el método o métodos abstractos. Utilice un protocolo que defina todos los métodos esperados en la abstracción, tanto los definidos como los que no. Luego, cree una extensión de protocolo que implemente los métodos que se definen en su caso. Finalmente, cualquier clase derivada debe implementar el protocolo, lo que significa todos los métodos, pero los que forman parte de la extensión del protocolo ya tienen su implementación.

Extendiendo su ejemplo con una función 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.
    }
}

Tenga en cuenta que al hacer esto, el compilador vuelve a ser su amigo. Si el método no se "anula", obtendrá un error en el momento de la compilación, en lugar de los que obtendrá fatalError()o las excepciones que sucederán en el tiempo de ejecución.

Jorge Ortiz
fuente
1
Imo la mejor respuesta.
Apfelsaft
1
Buena respuesta, pero su abstracción base no es capaz de almacenar propiedades, sin embargo
Joehinkle11
5

Entiendo lo que estás haciendo ahora, creo que sería mejor usar un protocolo

protocol BaseProtocol {
    func abstractFunction()
}

Luego, simplemente se ajusta al protocolo:

class SubClass : BaseProtocol {

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

Si su clase también es una subclase, los protocolos siguen la Superclase:

class SubClass: SuperClass, ProtocolOne, ProtocolTwo {}
Logan
fuente
3

Uso de assertpalabras clave para imponer métodos abstractos:

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

Sin embargo, como Steve Waddicor mencionó, probablemente quieras un protocollugar.

Erik
fuente
El beneficio de los métodos abstractos es que las comprobaciones se realizan en tiempo de compilación.
Fabrizio Bartolomucci
no use la función de aserción, porque al archivar, obtendrá el error 'Falta el retorno en una función que se espera que regrese'. Use fatalError ("Este método debe ser anulado por la subclase")
Zaporozhchenko Oleksandr
2

Entiendo la pregunta y estaba buscando la misma solución. Los protocolos no son lo mismo que los métodos abstractos.

En un protocolo, debe especificar que su clase se ajusta a dicho protocolo, un método abstracto significa que debe anular dicho método.

En otras palabras, los protocolos son una especie de opciones, debe especificar la clase base y el protocolo; si no especifica el protocolo, no tiene que anular dichos métodos.

Un método abstracto significa que desea una clase base pero necesita implementar su propio método o dos, que no es lo mismo.

Necesito el mismo comportamiento, es por eso que estaba buscando una solución. Supongo que a Swift le falta esa característica.

Marvin Aguero
fuente
1

Hay otra alternativa a este problema, aunque todavía hay un inconveniente en comparación con la propuesta de @ jaumard; Requiere una declaración de devolución. Aunque pierdo el punto de requerirlo, porque consiste en lanzar directamente una excepción:

class AbstractMethodException : NSException {

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

Y entonces:

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

Lo que venga después de eso es inalcanzable, así que no veo por qué forzar el regreso.

André Fratelli
fuente
Qué quiere decir AbstractMethodException().raise()?
Joshcodes
Err ... Probablemente. No puedo probar en este momento, pero si funciona de esa manera, entonces sí
André Fratelli
1

No sé si será útil, pero tuve un problema similar con el método abstracto al intentar construir el juego SpritKit. Lo que quería es una clase abstracta de Animal que tenga métodos como move (), run (), etc., pero los niños de la clase deberían proporcionar nombres de sprites (y otras funciones). Así que terminé haciendo algo como esto (probado para 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"]
        }
    }
}
interrumpir
fuente