Aulas abstratas em Swift Language


140

Existe uma maneira de criar uma classe abstrata na linguagem Swift, ou isso é uma limitação como Objective-C? Eu gostaria de criar uma classe abstrata comparável ao que Java define como uma classe abstrata.


Você precisa que a classe completa seja abstrata ou apenas alguns métodos? Veja a resposta aqui para métodos e propriedades individuais. stackoverflow.com/a/39038828/2435872 . Em Java, você pode ter classes abstratas, que não possuem nenhum dos métodos abstratos. Esse recurso especial não é fornecido pela Swift.
Jboi 19/08/16

Respostas:


174

Não há classes abstratas no Swift (assim como Objective-C). Sua melhor aposta será usar um protocolo , que é como uma interface Java.

Com o Swift 2.0, você pode adicionar implementações de métodos e implementações de propriedades calculadas usando extensões de protocolo. Suas únicas restrições são que você não pode fornecer variáveis ​​ou constantes de membros e não há despacho dinâmico .

Um exemplo dessa técnica seria:

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

Observe que isso está fornecendo recursos de "classe abstrata", mesmo para estruturas, mas as classes também podem implementar o mesmo protocolo.

Observe também que toda classe ou estrutura que implementa o protocolo Employee precisará declarar a propriedade annualSalary novamente.

Mais importante, observe que não há despacho dinâmico . Quando logSalaryé chamado na instância que é armazenada como SoftwareEngineerchama a versão substituída do método. Quando logSalaryé chamado na instância após ser convertido em um Employee, ele chama a implementação original (não envia dinamicamente para a versão substituída, mesmo que a instância seja realmente a Software Engineer.

Para obter mais informações, consulte o excelente vídeo da WWDC sobre esse recurso: Criando aplicativos melhores com tipos de valor no Swift


3
protocol Animal { var property : Int { get set } }. Você também pode deixar o set se você não quer que a propriedade de ter um setter
drewag

3
Eu acho que este vídeo WWDC é ainda mais relevante
Mario Zannone

2
@MarioZannone esse vídeo simplesmente me impressionou e me fez apaixonar por Swift.
Scott H

3
Se você apenas adicionar func logSalary()à declaração de protocolo do funcionário, o exemplo será impresso overriddenpara as duas chamadas para logSalary(). Isso está no Swift 3.1. Assim, você obtém os benefícios do polimorfismo. O método correto é chamado nos dois casos.
Mike Taverne

1
A regra sobre o envio dinâmico é esta ... se o método for definido apenas na extensão, será enviado estaticamente. Se também estiver definido no protocolo que você está estendendo, será enviado dinamicamente. Não há necessidade de tempos de execução de Objective-C. Esse é um comportamento puro do Swift.
Mark-Donohoe

47

Observe que esta resposta está direcionada ao Swift 2.0 e superior

Você pode obter o mesmo comportamento com protocolos e extensões de protocolo.

Primeiro, você escreve um protocolo que atua como uma interface para todos os métodos que precisam ser implementados em todos os tipos que estejam em conformidade.

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

Em seguida, você pode adicionar um comportamento padrão a todos os tipos que estão em conformidade com ele

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

Agora você pode criar novos tipos implementando Drivable.

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

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

Então, basicamente, você obtém:

  1. Verificações de tempo de compilação que garantem que todos os Drivableimplementemspeed
  2. Você pode implementar o comportamento padrão para todos os tipos que estão em conformidade com Drivable( accelerate)
  3. Drivable é garantido para não ser instanciado, pois é apenas um protocolo

Na verdade, esse modelo se comporta muito mais como características, o que significa que você pode estar em conformidade com vários protocolos e assumir implementações padrão de qualquer um deles, enquanto que com uma superclasse abstrata, você está limitado a uma hierarquia de classes simples.


Ainda assim, nem sempre existe a possibilidade de estender alguns protocolos, por exemplo UICollectionViewDatasource,. Gostaria de remover todo o clichê e encapsular em protocolo / extensão separados e depois reutilizá-lo por várias classes. Na verdade, o padrão modelo seria perfeito aqui, mas ...
Richard Topchii

1
Você não pode sobrescrever ˚acelerar˚ em ˚Car˚. Se você o fizer, a implementação em "Drives de Extensão" ainda será chamada sem nenhum aviso do compilador. Muito diferente de uma classe abstrata Java
Gerd Castan

@GerdCastan É verdade que as extensões de protocolo não suportam envio dinâmico.
IluTov

15

Eu acho que este é o mais próximo de Java abstractou C # abstract:

class AbstractClass {

    private init() {

    }
}

Observe que, para que os privatemodificadores funcionem, você deve definir esta classe em um arquivo Swift separado.

EDIT: Ainda assim, este código não permite declarar um método abstrato e, portanto, força sua implementação.


4
Ainda assim, isso não força uma subclasse a substituir uma função, além de ter uma implementação básica dessa função na classe pai.
Matthew Quiros

Em C #, se você implementar uma função em uma classe base abstrata, não será forçado a implementá-la em suas subclasses. Ainda assim, esse código não permite que você declare um método abstrato para forçar a substituição.
Teejay

Vamos dizer que ConcreteClass subclasse que AbstractClass. Como você instancia o ConcreteClass?
Javier Cadiz

2
ConcreteClass deve ter um construtor público. Você provavelmente precisa de um construtor protegido no AbstractClass, a menos que eles estejam no mesmo arquivo. Pelo que me lembro, o modificador de acesso protegido não existe no Swift. Portanto, a solução é declarar ConcreteClass no mesmo arquivo.
Teejay

13

A maneira mais simples é usar uma chamada para fatalError("Not Implemented")o método abstrato (não variável) na extensão do 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()

Esta é uma ótima resposta. Eu não achei que funcionaria se você ligasse, (MyConcreteClass() as MyInterface).myMethod()mas funciona! A chave está incluída myMethodna declaração do protocolo; caso contrário, a chamada trava.
Mike Taverne

11

Depois de lutar por várias semanas, finalmente percebi como traduzir uma classe abstrata Java / PHP para 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());
    }
}

No entanto, acho que a Apple não implementou classes abstratas porque geralmente usa o padrão delegate + protocol. Por exemplo, o mesmo padrão acima seria melhor assim:

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

Eu precisava desse tipo de padrão porque queria comumizar alguns métodos no UITableViewController, como viewWillAppear etc. Isso foi útil?


1
+1 Estava planejando fazer exatamente a mesma abordagem que você mencionou primeiro; ponteiro interessante para o padrão de delegação.
Angad

Além disso, ajudaria se os dois exemplos estivessem no mesmo caso de uso. GoldenSpoonChild é um nome um pouco confuso, especialmente porque a mãe parece estar estendendo-o.
Angad

@ Angad O padrão delegate é o mesmo caso de uso, porém não é uma tradução; é um padrão diferente, por isso deve ter uma perspectiva diferente.
21717 Josh Woodcock

8

Existe uma maneira de simular classes abstratas usando Protocolos. Isto é um exemplo:

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

Mais uma maneira de implementar a classe abstrata é bloquear o inicializador. Eu fiz dessa maneira:

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
Isso não fornece garantias e / ou verificações. Explodir durante o tempo de execução é uma maneira ruim de impor regras. É melhor ter o init como privado.
Морт

As classes abstratas também devem ter suporte para métodos abstratos.
Cristik

@ Cristik mostrei a idéia principal, não é uma solução completa. Dessa forma, você pode não gostar de 80% das respostas, porque elas não são detalhadas o suficiente para a sua situação
Alexey Yarmolovich

1
@AlexeyYarmolovich, que diz que não estou detestando 80% das respostas? :) Brincando à parte, eu estava sugerindo que seu exemplo pode ser aprimorado, isso ajudará outros leitores e ajudará você a obter votos positivos.
Cristik

0

Eu estava tentando criar uma Weatherclasse abstrata, mas o uso de protocolos não era o ideal, pois tive que escrever os mesmos initmétodos repetidamente. Estender o protocolo e escrever um initmétodo teve seus problemas, principalmente porque eu estava usando a NSObjectconformidade NSCoding.

Então, eu vim com isso para a NSCodingconformidade:

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

Quanto a init :

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

0

Mova todas as referências para propriedades abstratas e métodos da classe Base para implementação de extensão de protocolo, em que Auto-restrição para a classe Base. Você terá acesso a todos os métodos e propriedades da classe Base. Adicionalmente, o compilador verifica a implementação de métodos e propriedades abstratas no protocolo para classes 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;
  }
}

0

Com a limitação de nenhum despacho dinâmico, você pode fazer algo assim:

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()
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.