Clases de resumen en lenguaje rápido

140

¿Hay alguna manera de crear una clase abstracta en el lenguaje Swift o es una limitación al igual que Objective-C? Me gustaría crear una clase abstracta comparable a lo que Java define como una clase abstracta.

kev
fuente
¿Necesita que la clase completa sea abstracta o solo algunos métodos? Vea la respuesta aquí para métodos y propiedades individuales. stackoverflow.com/a/39038828/2435872 . En Java puede tener clases abstractas, que no tienen ninguno de los métodos abstractos. Esa característica especial no es proporcionada por Swift.
jboi

Respuestas:

174

No hay clases abstractas en Swift (al igual que Objective-C). Su mejor opción será utilizar un protocolo , que es como una interfaz Java.

Con Swift 2.0, puede agregar implementaciones de métodos e implementaciones de propiedades calculadas utilizando extensiones de protocolo. Sus únicas restricciones son que no puede proporcionar variables miembro o constantes y no hay despacho dinámico .

Un ejemplo de esta técnica sería:

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

Tenga en cuenta que esto proporciona características de "clase abstracta" incluso para estructuras, pero las clases también pueden implementar el mismo protocolo.

Observe también que cada clase o estructura que implemente el protocolo del Empleado deberá declarar nuevamente la propiedad del Salario anual.

Lo más importante, tenga en cuenta que no hay despacho dinámico . Cuando logSalaryse llama en la instancia que se almacena como SoftwareEngineer, llama a la versión anulada del método. Cuando se invoca logSalaryen la instancia después de que se ha convertido en un Employee, llama a la implementación original (no se envía dinámicamente a la versión anulada, aunque la instancia es realmente un Software Engineer.

Para obtener más información, consulte el excelente video de WWDC sobre esa característica: Crear mejores aplicaciones con tipos de valor en Swift

Drewag
fuente
3
protocol Animal { var property : Int { get set } }. También puede omitir el conjunto si no desea que la propiedad tenga un setter
drewag
3
Creo que este video de wwdc es aún más relevante
Mario Zannone
2
@MarioZannone ese video me dejó boquiabierto y me enamoró de Swift.
Scott H
3
Si solo agrega func logSalary()a la declaración de protocolo de empleado, el ejemplo se imprime overriddenpara ambas llamadas a logSalary(). Esto está en Swift 3.1. Así obtienes los beneficios del polimorfismo. Se llama al método correcto en ambos casos.
Mike Taverne
1
La regla sobre el despacho dinámico es esta ... si el método se define solo en la extensión, entonces se despacha estáticamente. Si también se define en el protocolo que está extendiendo, se envía dinámicamente. No hay necesidad de tiempos de ejecución Objective-C. Este es un comportamiento puro de Swift.
Mark A. Donohoe
47

Tenga en cuenta que esta respuesta está dirigida a Swift 2.0 y superior

Puede lograr el mismo comportamiento con protocolos y extensiones de protocolo.

Primero, escribe un protocolo que actúa como una interfaz para todos los métodos que deben implementarse en todos los tipos que se ajustan a él.

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

Luego, puede agregar un comportamiento predeterminado a todos los tipos que lo conforman

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

Ahora puede crear nuevos tipos mediante la implementación Drivable.

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

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

Entonces básicamente obtienes:

  1. Verificaciones de tiempo de compilación que garantizan que todos se Drivableimplementenspeed
  2. Puede implementar el comportamiento predeterminado para todos los tipos que se ajusten a Drivable( accelerate)
  3. Drivable está garantizado para no ser instanciado ya que es solo un protocolo

Este modelo en realidad se comporta mucho más como rasgos, lo que significa que puede ajustarse a múltiples protocolos y asumir implementaciones predeterminadas de cualquiera de ellos, mientras que con una superclase abstracta está limitado a una jerarquía de clases simple.

IluTov
fuente
Aún así, no siempre existe la posibilidad de extender algunos protocolos, por ejemplo UICollectionViewDatasource,. Me gustaría eliminar todo el repetitivo y encapsularlo en un protocolo / extensión separado y luego reutilizarlo en varias clases. De hecho, el patrón de plantilla sería perfecto aquí, pero ...
Richard Topchii
1
No puede sobrescribir ˚accelerate˚ en ˚Car˚. Si lo hace, la implementación en ˚extentsion Driveable˚ todavía se llama sin ninguna advertencia del compilador. Muy diferente a una clase abstracta de Java
Gerd Castan
@GerdCastan Cierto, las extensiones de protocolo no admiten el despacho dinámico.
IluTov
15

Creo que esto es lo más cercano a Java abstracto C # abstract:

class AbstractClass {

    private init() {

    }
}

Tenga en cuenta que, para que los privatemodificadores funcionen, debe definir esta clase en un archivo Swift separado.

EDITAR: Aún así, este código no permite declarar un método abstracto y, por lo tanto, forzar su implementación.

Teejay
fuente
44
Aún así, esto no obliga a una subclase a anular una función mientras que también tiene una implementación base de esa función en la clase principal.
Matthew Quiros
En C #, si implementa una función en una clase base abstracta, no está obligado a implementarla en sus subclases. Aún así, este código no le permite declarar un método abstracto para forzar la anulación.
Teejay
Digamos que ConcreteClass subclase que AbstractClass. ¿Cómo instanciar ConcreteClass?
Javier Cádiz el
2
ConcreteClass debería tener un constructor público. Probablemente necesite un constructor protegido en AbstractClass, a menos que estén en el mismo archivo. Según lo que recuerdo, el modificador de acceso protegido no existe en Swift. Entonces la solución es declarar ConcreteClass en el mismo archivo.
Teejay
13

La forma más simple es utilizar una llamada al fatalError("Not Implemented")método abstracto (no variable) en la extensión del protocolo.

protocol MyInterface {
    func myMethod() -> String
}


extension MyInterface {

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

}

class MyConcreteClass: MyInterface {

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

}

MyConcreteClass().myMethod()
Carlos García
fuente
Esta es una respuesta genial. ¡No pensé que funcionaría si llamaras, (MyConcreteClass() as MyInterface).myMethod()pero lo hace! La clave está incluida myMethoden la declaración del protocolo; de lo contrario, la llamada se bloquea.
Mike Taverne
11

Después de luchar durante varias semanas, finalmente me di cuenta de cómo traducir una clase abstracta de Java / PHP a 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());
    }
}

Sin embargo, creo que Apple no implementó clases abstractas porque generalmente usa el patrón delegado + protocolo en su lugar. Por ejemplo, el mismo patrón anterior se haría mejor así:

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

Necesitaba este tipo de patrón porque quería comúnizar algunos métodos en UITableViewController como viewWillAppear, etc. ¿Fue útil?

Josh Woodcock
fuente
1
+1 Estaba planeando hacer exactamente el mismo enfoque que mencionas primero; puntero interesante al patrón de delegación.
Angad
Además, sería útil que ambos ejemplos estuvieran en el mismo caso de uso. GoldenSpoonChild es un nombre un poco confuso, especialmente dado que Madre parece estar extendiéndolo.
Angad
@Angad El patrón de delegado es el mismo caso de uso, sin embargo, no es una traducción; Es un patrón diferente, por lo que debe tener una perspectiva diferente.
Josh Woodcock
8

Hay una manera de simular clases abstractas usando Protocolos. Esto es un ejemplo:

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
    }
}
David Seca
fuente
1

Una forma más de cómo puede implementar la clase abstracta es bloquear el inicializador. Lo he hecho de esta manera:

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")
        }
    }
}
Alexey Yarmolovich
fuente
44
Esto no proporciona ninguna garantía y / o control. Volar durante el tiempo de ejecución es una mala manera de hacer cumplir las reglas. Es mejor tener el init como privado.
Морт
Las clases abstractas también deben tener soporte para métodos abstractos.
Cristik
@Cristik Mostré la idea principal, no es la solución completa. De esta manera, puede que no le guste el 80% de las respuestas porque no están lo suficientemente detalladas para su situación
Alexey Yarmolovich
1
@AlexeyYarmolovich, ¿quién dice que no me gusta el 80% de las respuestas? :) Bromas aparte, estaba sugiriendo que tu ejemplo puede mejorarse, esto ayudará a otros lectores y te ayudará a obtener votos positivos.
Cristik
0

Estaba tratando de hacer una Weatherclase abstracta, pero el uso de protocolos no era ideal ya que tenía que escribir los mismos initmétodos una y otra vez. Extender el protocolo y escribir un initmétodo tenía sus problemas, especialmente porque estaba usando la NSObjectconformidad con NSCoding.

Entonces se me ocurrió esto por la NSCodingconformidad:

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

En cuanto a init:

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

Mueva todas las referencias a propiedades abstractas y métodos de la clase Base a la implementación de extensión de protocolo, donde Auto restricción a la clase Base. Obtendrá acceso a todos los métodos y propiedades de la clase Base. Además, el compilador verifica la implementación de métodos abstractos y propiedades en el protocolo para clases derivadas

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;
  }
}
john07
fuente
0

Con la limitación de ningún despacho dinámico, puede hacer algo como esto:

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()
Christo Smal
fuente