Funções abstratas na linguagem Swift

127

Eu gostaria de criar uma função abstrata em linguagem rápida. É possível?

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

class SubClass : BaseClass {
    override func abstractFunction() {
        // Override
    }
}
kev
fonte
Isso é bem próximo da sua outra pergunta, mas a resposta aqui parece um pouco melhor.
David Berry
As perguntas são semelhantes, mas as soluções são muito diferentes porque uma classe abstrata tem casos de uso diferentes de uma função abstrata.
kev
Sim, por que não votei para fechar qualquer um, mas as respostas não haverá além útil "você não pode" Aqui você tem a melhor resposta disponível :)
David Berry

Respostas:

198

Não há conceito abstrato em Swift (como Objective-C), mas você pode fazer isso:

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

class SubClass : BaseClass {
     override func abstractFunction() {
         // Override
     } 
}
jaumard
fonte
13
Alternativamente, você pode usar assert(false, "This method must be overriden by the subclass").
Erik
20
oufatalError("This method must be overridden")
nathan
5
asserções exigirão uma declaração de retorno quando um método tiver um tipo de retorno. Acho fatalError () é melhor para isso uma razão
André Fratelli
6
Também há preconditionFailure("Bla bla bla")no Swift que será executado nas versões de lançamento e também elimina a necessidade de uma declaração de retorno. EDIT: Só descobri que este método é basicamente igual fatalError(), mas é a forma mais adequada (Better documentação, introduzido em Swift junto com precondition(), assert()e assertionFailure(), leia aqui )
Kametrixom
2
e se a função tiver um tipo de retorno?
LoveMeow
36

O que você deseja não é uma classe base, mas um protocolo.

protocol MyProtocol {
    func abstractFunction()
}

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

Se você não fornecer abstractFunction em sua classe, é um erro.

Se você ainda precisar da classe básica para outro comportamento, poderá fazer o seguinte:

class MyClass : BaseClass, MyProtocol {
    func abstractFunction() {
    }
}
Steve Waddicor
fonte
23
Isso não funciona muito bem. Se o BaseClass quiser chamar essas funções vazias, também será necessário implementar o protocolo e, em seguida, implementar as funções. Além disso, a subclasse ainda não é obrigada a implementar as funções em tempo de compilação e você também não é obrigado a implementar o protocolo.
bandeja paisa
5
Esse é o meu ponto ... BaseClass não tem nada a ver com o protocolo e, para agir como uma classe abstrata, deve ter algo a ver com ele. Para esclarecer o que escrevi "Se o BaseClass quiser chamar essas funções vazias, também será necessário implementar o protocolo que forçará você a implementar as funções - o que levará a subclasse a não ser forçada a implementar as funções em tempo de compilação, porque o BaseClass já o fez ".
bandeja paisa
4
Isso realmente depende de como você define "abstrato". O ponto principal de uma função abstrata é que você pode colocá-la em uma classe que pode fazer coisas normais da classe. Por exemplo, a razão pela qual desejo isso é porque preciso definir um membro estático que você não consegue fazer com os protocolos. Portanto, é difícil para mim ver que isso atende ao ponto útil de uma função abstrata.
Filhote de cachorro
3
Penso que a preocupação com os comentários acima mencionados é que isso não está de acordo com o princípio da Substituição de Liskov, que afirma que "objetos em um programa devem ser substituíveis por instâncias de seus subtipos sem alterar a correção desse programa". Supõe-se que este seja um tipo abstrato. Isso é particularmente importante no caso do padrão Factory Method, em que a classe base é responsável por criar e armazenar propriedades originárias da subclasse.
David James
2
Uma classe base abstrata e um protocolo não são a mesma coisa.
Shoerob
29

Eu porto uma quantidade razoável de código de uma plataforma que suporta classes base abstratas para o Swift e me deparo muito com isso. Se o que você realmente deseja é a funcionalidade de uma classe base abstrata, isso significa que essa classe serve tanto como uma implementação da funcionalidade de classe baseada em compartilhamento (caso contrário, seria apenas uma interface / protocolo) E define métodos que devem ser implementados por classes derivadas.

Para fazer isso no Swift, você precisará de um protocolo e uma classe base.

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

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

Observe que a classe base implementa o (s) método (s) compartilhado (s), mas não implementa o protocolo (uma vez que não implementa todos os métodos).

Então, na classe derivada:

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

A classe derivada herda sharedFunction da classe base, ajudando-a a satisfazer essa parte do protocolo, e o protocolo ainda exige que a classe derivada implemente abstractFunction.

A única desvantagem real desse método é que, como a classe base não implementa o protocolo, se você tiver um método da classe base que precise acessar uma propriedade / método do protocolo, será necessário substituí-lo na classe derivada e, a partir daí, chamar a classe base (via super) passando selfpara que a classe base tenha uma instância do protocolo com o qual realizar seu trabalho.

Por exemplo, digamos que sharedFunction necessário para chamar abstractFunction. O protocolo permaneceria o mesmo e as classes agora pareceriam:

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

Agora, o sharedFunction da classe derivada está satisfazendo essa parte do protocolo, mas a classe derivada ainda é capaz de compartilhar a lógica da classe base de uma maneira razoavelmente direta.

BobDickinson
fonte
4
Grande compreensão e boa profundidade sobre "a classe base não implementa o protocolo" ... que é o tipo de objetivo de uma classe abstrata. Estou confuso que esse recurso básico de OO esteja ausente, mas, novamente, fui criado em Java.
Dan Rosenstark
2
No entanto, a implementação não permite implementar perfeitamente o método Template Function, em que o método template chama um grande número de métodos abstratos implementados nas subclasses. Nesse caso, você deve escrevê-los como métodos normais na superclasse, como métodos no protocolo e novamente como implementações na subclasse. Na prática, você deve escrever três vezes o mesmo material e confiar apenas na verificação de substituição para garantir que não cometa erros ortográficos desastrosos! Eu realmente espero que agora que o Swift esteja aberto para desenvolvedores, a função abstrata completa seja introduzida.
Fabrizio Bartolomucci
1
Tanto tempo e o código pode ser salvo com a introdução da palavra-chave "protected".
Shoerob
23

Esta parece ser a maneira "oficial" de como a Apple está lidando com métodos abstratos no UIKit. Dê uma olhada UITableViewControllere como ele está trabalhando UITableViewDelegate. Uma das primeiras coisas que você faz, é adicionar uma linha: delegate = self. Bem, esse é exatamente o truque.

1. Coloque o método abstrato em um protocolo
protocol AbstractMethodsForClassX {
    func abstractMethod() -> String
}
2. Escreva sua 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. Escreva a subclasse (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!"}
}
Aqui está, como você usa tudo isso
let x = ClassX()
x.doSomethingWithAbstractMethod()

Verifique com o Playground para ver a saída.

Algumas observações

  • Primeiro, muitas respostas já foram dadas. Espero que alguém encontre todo o caminho até este.
  • A questão era, na verdade, encontrar um padrão que implementa:
    • Uma classe chama um método, que deve ser implementado em uma de suas subclasses derivadas (substituídas)
    • Na melhor das hipóteses, se o método não for substituído na subclasse, obtenha um erro durante o tempo de compilação
  • A questão dos métodos abstratos é que eles são uma mistura de uma definição de interface e parte de uma implementação real na classe base. Ambos ao mesmo tempo. Como o swift é muito novo e muito definido, ele não tem essa conveniência, mas conceitos "impuros" (ainda).
  • Para mim (um pobre e velho cara de Java), esse problema evolui de tempos em tempos. Eu li todas as respostas neste post e, desta vez, acho que encontrei um padrão que parece viável - pelo menos para mim.
  • Atualização : parece que os implementadores do UIKit na Apple usam o mesmo padrão. UITableViewControllerimplementa, UITableViewDelegatemas ainda precisa ser registrado como delegado, definindo explicitamente a delegatepropriedade
  • Tudo isso é testado no Playground do Xcode 7.3.1
jboi
fonte
Talvez não seja perfeito, porque tenho problemas em ocultar a interface da classe de outras classes, mas é o suficiente para implementar o método de fábrica clássico no Swift.
Tomasz Nazarenko
Hummm. Gosto muito mais da aparência desta resposta, mas também não tenho certeza de que seja adequada. Ou seja, eu tenho um ViewController que pode exibir informações para vários tipos diferentes de objetos, mas o objetivo de exibir essas informações é o mesmo, com os mesmos métodos, mas reunindo as informações de maneira diferente com base no objeto . Portanto, eu tenho uma classe de delegado pai para este VC e uma subclasse desse delegado para cada tipo de objeto. Instancio um delegado com base no objeto passado. Em um exemplo, cada objeto tem alguns comentários relevantes armazenados.
Jake T.
Então, eu tenho um getCommentsmétodo no delegado pai. Existem vários tipos de comentários e quero os relevantes para cada objeto. Portanto, quero que as subclasses não sejam compiladas quando o fizer, delegate.getComments()se não substituir esse método. O VC apenas sabe que possui um ParentDelegateobjeto chamado delegate, ou BaseClassXno seu exemplo, mas BaseClassXnão possui o método abstraído. Eu precisaria que o VC soubesse especificamente que está usando o SubclassedDelegate.
Jake T.
Espero te entender direito. Caso contrário, você se propõe a fazer uma nova pergunta onde pode adicionar algum código? Eu acho que você só precisa ter uma instrução if na verificação de alguma coisa com o método abstrato, se o delegado está definido ou não.
jboi
Vale ressaltar que atribuir a si próprio ao delegado cria um ciclo de retenção. Talvez seja melhor para declará-la como fraca (que é comumente uma boa idéia para os delegados)
Enricoza
7

Uma maneira de fazer isso é usar um fechamento opcional definido na classe base, e os filhos podem optar por implementá-lo ou não.

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

class SubClass : BaseClass {
    init()
    {
        super.init()
        abstractClosure={ ..... }
    }
}
hariseldon78
fonte
O que eu gosto nessa abordagem é que não preciso me lembrar de implementar um protocolo na classe herdada. Eu tenho uma classe base para meus ViewControllers e é assim que imponho a funcionalidade opcional específica do ViewController (por exemplo, o aplicativo se tornou ativo etc.) que pode ser invocado pela funcionalidade da classe base.
ProgrammierTier
6

Bem, eu sei que estou atrasado para o jogo e que posso estar aproveitando as mudanças que aconteceram. Me desculpe por isso.

De qualquer forma, gostaria de contribuir com minha resposta, porque adoro fazer testes e as soluções com o fatalError()AFAIK não são testáveis ​​e as que têm exceções são muito mais difíceis de testar.

Eu sugeriria usar uma abordagem mais rápida . Seu objetivo é definir uma abstração que possua alguns detalhes em comum, mas que não esteja totalmente definida, ou seja, o (s) método (s) abstrato (s). Use um protocolo que defina todos os métodos esperados na abstração, os que estão definidos e também os que não são. Em seguida, crie uma extensão de protocolo que implemente os métodos definidos no seu caso. Finalmente, qualquer classe derivada deve implementar o protocolo, o que significa todos os métodos, mas os que fazem parte da extensão do protocolo já têm sua implementação.

Estendendo seu exemplo com uma função 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.
    }
}

Observe que, ao fazer isso, o compilador é novamente seu amigo. Se o método não for "substituído", você receberá um erro no tempo de compilação, em vez daqueles com os quais você obteria fatalError()ou exceções que aconteceriam no tempo de execução.

Jorge Ortiz
fonte
1
Imo a melhor resposta.
Apfelsaft
1
Boa resposta, mas a sua abstração de base não é capaz de propriedades de armazenamento, no entanto
joehinkle11
5

Entendo o que você está fazendo agora, acho melhor você usar um protocolo

protocol BaseProtocol {
    func abstractFunction()
}

Então, você apenas está em conformidade com o protocolo:

class SubClass : BaseProtocol {

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

Se a sua classe também for uma subclasse, os protocolos seguirão a superclasse:

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

Usando a assertpalavra-chave para aplicar métodos abstratos:

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

No entanto, como Steve Waddicor mencionou, você provavelmente quer um protocol.

Erik
fonte
O benefício dos métodos abstratos é que as verificações são feitas em tempo de compilação.
Fabrizio Bartolomucci
não use assert, porque no arquivamento, você receberá o erro 'Retorno ausente em uma função que deve retornar'. Use fatalError ("Este método deve ser substituído pela subclasse")
Zaporozhchenko Oleksandr
2

Eu entendi a pergunta e estava procurando a mesma solução. Protocolos não são os mesmos que métodos abstratos.

Em um protocolo, você precisa especificar que sua classe está em conformidade com esse protocolo, um método abstrato significa que você deve substituir esse método.

Em outras palavras, os protocolos são opcionais, você precisa especificar a classe base e o protocolo; se você não especificar o protocolo, não precisará substituir esses métodos.

Um método abstrato significa que você deseja uma classe base, mas precisa implementar seu próprio método ou dois, o que não é o mesmo.

Eu preciso do mesmo comportamento, por isso estava procurando uma solução. Eu acho que Swift está faltando esse recurso.

Marvin Aguero
fonte
1

Há outra alternativa para esse problema, embora ainda haja uma desvantagem quando comparada à proposta de @ jaumard; requer uma declaração de retorno. Embora eu perca o ponto de exigi-lo, porque consiste em lançar diretamente uma exceção:

class AbstractMethodException : NSException {

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

E depois:

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

O que vier depois disso é inacessível, então não vejo por que forçar o retorno.

André Fratelli
fonte
Você quer dizer AbstractMethodException().raise()?
21715 Joshcodes
Err ... Provavelmente. Eu não posso testar agora, mas se funcionar dessa maneira, então sim
André Fratelli
1

Não sei se será útil, mas tive um problema semelhante com o método abstraído ao tentar criar o jogo SpritKit. O que eu queria é uma classe Animal abstrata que possua métodos como move (), run () etc, mas nomes de sprites (e outras funcionalidades) devem ser fornecidos pelos filhos da classe. Então, acabei fazendo algo assim (testado para o 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"]
        }
    }
}
interromper
fonte