Абстрактные функции в Swift Language


127

Я хотел бы создать абстрактную функцию на быстром языке. Является ли это возможным?

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

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

Это довольно близко к вашему другому вопросу, но ответ здесь кажется немного лучше.
Дэвид Берри

Вопросы похожи, но решения сильно отличаются, потому что у абстрактного класса разные варианты использования, чем у абстрактной функции.
kev

Ага, почему я тоже не проголосовал за закрытие, но ответы там не будут полезны, кроме «вы не можете». Вот вам лучший из доступных ответов :)
Дэвид Берри

Ответы:


198

В Swift нет концепции абстрактного (например, Objective-C), но вы можете сделать это:

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

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

13
В качестве альтернативы вы можете использовать assert(false, "This method must be overriden by the subclass").
Эрик

20
илиfatalError("This method must be overridden")
Натан 08

5
утверждения потребуют оператора возврата, если метод имеет тип возврата. Я думаю, что fatalError () лучше именно по этой причине
Андре Фрателли

6
Также есть preconditionFailure("Bla bla bla")Swift, который будет выполняться в сборках релизов, а также устраняет необходимость в операторе возврата. EDIT: Только что узнал , что этот метод в основном равно , fatalError()но это более правильный способ (лучшая документация, представленная в Swift вместе с precondition(), assert()и assertionFailure(), читайте здесь )
Kametrixom

2
что, если функция имеет возвращаемый тип?
LoveMeow

36

Вам нужен не базовый класс, а протокол.

protocol MyProtocol {
    func abstractFunction()
}

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

Если вы не предоставляете abstractFunction в своем классе, это ошибка.

Если вам все еще нужен базовый класс для другого поведения, вы можете сделать это:

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

23
Это не совсем работает. Если BaseClass хочет вызвать эти пустые функции, ему также придется реализовать протокол, а затем реализовать функции. Кроме того, подкласс по-прежнему не обязан реализовывать функции во время компиляции, и вы также не обязаны реализовывать протокол.
bandejapaisa

5
Это моя точка зрения ... BaseClass не имеет ничего общего с протоколом, и, чтобы действовать как абстрактный класс, он должен иметь какое-то отношение к нему. Чтобы прояснить то, что я написал: «Если BaseClass хочет вызвать эти пустые функции, ему также придется реализовать протокол, который заставит вас реализовать функции - что затем приведет к тому, что подкласс не будет вынужден реализовывать функции во время компиляции, потому что BaseClass уже сделал это ".
bandejapaisa

4
Это действительно зависит от того, как вы определяете «абстрактное». Весь смысл абстрактной функции в том, что вы можете поместить ее в класс, который может выполнять обычные действия класса. Например, я хочу этого потому, что мне нужно определить статический член, который вы, похоже, не можете делать с протоколами. Поэтому мне трудно понять, что это соответствует полезной точке абстрактной функции.
Щенок

3
Я думаю, что вышеупомянутые комментарии вызывают озабоченность тем, что это не соответствует принципу подстановки Лискова, который гласит, что «объекты в программе должны заменяться экземплярами их подтипов без изменения правильности этой программы». Предполагается, что это абстрактный тип. Это особенно важно в случае шаблона Factory Method, где базовый класс отвечает за создание и хранение свойств, которые происходят из подкласса.
Дэвид Джеймс

2
Абстрактный базовый класс и протокол - это не одно и то же.
Shoerob

29

Я переношу изрядное количество кода с платформы, которая поддерживает абстрактные базовые классы, на Swift и часто использую его. Если то, что вы действительно хотите, - это функциональность абстрактного базового класса, то это означает, что этот класс служит как реализацией функциональности общего класса (в противном случае это был бы просто интерфейс / протокол), И он определяет методы, которые должны быть реализованы производные классы.

Для этого в Swift вам понадобится протокол и базовый класс.

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

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

Обратите внимание, что базовый класс реализует общие методы, но не реализует протокол (поскольку он не реализует все методы).

Затем в производном классе:

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

Производный класс наследует sharedFunction от базового класса, помогая ему удовлетворить эту часть протокола, и протокол по-прежнему требует, чтобы производный класс реализовал abstractFunction.

Единственный реальный недостаток этого метода заключается в том, что, поскольку базовый класс не реализует протокол, если у вас есть метод базового класса, которому требуется доступ к свойству / методу протокола, вам придется переопределить его в производном классе и оттуда вызвать передача базового класса (через super), selfчтобы у базового класса был экземпляр протокола, с которым он мог бы выполнять свою работу.

Например, допустим, что sharedFunction необходимо вызвать abstractFunction. Протокол останется прежним, а классы теперь будут выглядеть так:

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

Теперь sharedFunction из производного класса удовлетворяет эту часть протокола, но производный класс по-прежнему может совместно использовать логику базового класса достаточно простым способом.


4
Отличное понимание и хорошая глубина в том, что «базовый класс не реализует протокол» ... что является своего рода точкой для абстрактного класса. Я сбит с толку, что эта базовая функция объектно-ориентированного программирования отсутствует, но, опять же, я вырос на Java.
Дэн Розенстарк

2
Тем не менее, реализация не позволяет беспрепятственно реализовать метод функции шаблона, в котором метод шаблона вызывает большое количество абстрактных методов, реализованных в подклассах. В этом случае вы должны записать их как обычные методы в суперклассе, как методы в протоколе, так и как реализации в подклассе. На практике вам придется трижды написать одно и то же и полагаться только на проверку переопределения, чтобы быть уверенным в том, что не произойдет каких-либо катастрофических ошибок! Я очень надеюсь, что теперь, когда Swift открыт для разработчиков, будут представлены полноценные абстрактные функции.
Фабрицио Бартоломуччи

1
Столько времени и кода можно было бы сэкономить, введя ключевое слово «protected».
Shoerob

23

Похоже, это «официальный» способ, которым Apple обрабатывает абстрактные методы в UIKit. Взгляните на UITableViewControllerто, как он работает UITableViewDelegate. Одна из самых первых вещей , которые вы делаете, чтобы добавить строку: delegate = self. Что ж, в этом и весь фокус.

1. Поместите абстрактный метод в протокол
protocol AbstractMethodsForClassX {
    func abstractMethod() -> String
}
2. Напишите свой базовый класс
/// 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. Напишите подкласс (ы).
/// 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!"}
}
Вот как все это использовать
let x = ClassX()
x.doSomethingWithAbstractMethod()

Проверьте с Playground, чтобы увидеть результат.

Некоторые замечания

  • Во-первых, уже было дано много ответов. Я надеюсь, что кто-нибудь найдет вплоть до этого.
  • На самом деле вопрос заключался в том, чтобы найти шаблон, реализующий:
    • Класс вызывает метод, который должен быть реализован в одном из его производных подклассов (переопределен)
    • В лучшем случае, если метод не переопределен в подклассе, получить ошибку во время компиляции
  • Суть абстрактных методов в том, что они представляют собой смесь определения интерфейса и части фактической реализации в базовом классе. И то и другое одновременно. Поскольку swift является очень новым и очень четким определением, у него нет такого удобства, кроме "нечистых" концепций (пока).
  • Для меня (бедного старого Java-парня) эта проблема время от времени развивается. Я прочитал все ответы в этом посте, и на этот раз я думаю, что нашел шаблон, который выглядит выполнимым - по крайней мере, для меня.
  • Обновление : похоже, что разработчики UIKit в Apple используют тот же шаблон. UITableViewControllerреализует, UITableViewDelegateно по-прежнему должен быть зарегистрирован как делегат, явно установив delegateсвойство.
  • Все это проверено на игровой площадке Xcode 7.3.1

Может быть, не идеально, потому что у меня проблемы со скрытием интерфейса класса от других классов, но этого достаточно, чтобы реализовать классический Factory Method в Swift.
Tomasz Nazarenko

Хммм. Мне намного больше нравится внешний вид этого ответа, но я также не уверен, что он подходит. То есть у меня есть ViewController, который может отображать информацию для нескольких различных типов объектов, но цель отображения этой информации одинакова, с использованием всех тех же методов, но извлечение и сбор информации по-разному в зависимости от объекта. , Итак, у меня есть родительский класс делегата для этого VC и подкласс этого делегата для каждого типа объекта. Я создаю экземпляр делегата на основе переданного объекта. В одном примере для каждого объекта сохранены некоторые соответствующие комментарии.
Джейк Т.

Итак, у меня есть getCommentsметод родительского делегата. Есть несколько типов комментариев, и мне нужны те, которые относятся к каждому объекту. Итак, я хочу, чтобы подклассы не компилировались, когда я это делаю, delegate.getComments()если я не отменяю этот метод. VC просто знает, что у него есть ParentDelegateвызываемый объект delegate, или BaseClassXв вашем примере, но BaseClassXне имеет абстрактного метода. Мне нужен VC, чтобы точно знать, что он использует SubclassedDelegate.
Джейк Т.

Надеюсь, я правильно вас понял. Если нет, не могли бы вы задать новый вопрос, куда можно добавить код? Я думаю, вам просто нужно иметь if-оператор в проверке doSomethingWithAbstractMethod, установлен ли делегат или нет.
jboi

Стоит упомянуть, что назначение делегату self создает цикл сохранения. Может быть, лучше объявить его слабым (что обычно является хорошей идеей для делегатов)
Энрикоса

7

Один из способов сделать это - использовать необязательное замыкание, определенное в базовом классе, и дочерние элементы могут выбирать, реализовывать его или нет.

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

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

Что мне нравится в этом подходе, так это то, что мне не нужно помнить о реализации протокола в наследующем классе. У меня есть базовый класс для моих ViewControllers, и именно так я применяю дополнительные функции ViewController (например, приложение стало активным и т. Д.), Которые могут быть вызваны функциональностью базового класса.
ProgrammierTier

6

Что ж, я знаю, что опаздываю в игру и, возможно, использую произошедшие изменения. Извини за это.

В любом случае, я хотел бы внести свой ответ, потому что мне нравится проводить тестирование, и решения с fatalError(), AFAIK, не тестируются, а те, которые имеют исключения, намного сложнее тестировать.

Я бы посоветовал использовать более быстрый подход. Ваша цель - определить абстракцию, которая имеет некоторые общие детали, но не полностью определена, то есть абстрактный метод (ы). Используйте протокол, который определяет все ожидаемые методы в абстракции, как те, которые определены, так и те, которые не определены. Затем создайте расширение протокола, реализующее методы, определенные в вашем случае. Наконец, любой производный класс должен реализовывать протокол, что означает все методы, но те, которые являются частью расширения протокола, уже имеют свою реализацию.

Расширение вашего примера одной конкретной функцией:

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

Обратите внимание, что, делая это, компилятор снова становится вашим другом. Если метод не «переопределен», вы получите ошибку во время компиляции вместо тех, которые вы бы получили, fatalError()или исключений, которые произошли бы во время выполнения.


1
Имо лучший ответ.
Apfelsaft 09

1
Хороший ответ, но ваша базовая абстракция не способна хранить свойства,
joehinkle11

5

Я понимаю, что вы сейчас делаете, думаю, вам лучше воспользоваться протоколом

protocol BaseProtocol {
    func abstractFunction()
}

Затем вы просто соответствуете протоколу:

class SubClass : BaseProtocol {

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

Если ваш класс также является подклассом, протоколы следуют за суперклассом:

class SubClass: SuperClass, ProtocolOne, ProtocolTwo {}

3

Использование assertключевого слова для принудительного применения абстрактных методов:

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

Однако, как сказал Стив Ваддикор, вы, вероятно, захотите protocolвзамен.


Преимущество абстрактных методов в том, что проверки выполняются во время компиляции.
Фабрицио Бартоломуччи

не используйте assert, потому что при архивировании u получит ошибку «Отсутствует возврат в функции, которая должна вернуть». Use fatalError («Этот метод должен быть переопределен подклассом»)
Запорожченко Александр

2

Я понимаю вопрос и искал такое же решение. Протоколы - это не то же самое, что абстрактные методы.

В протоколе вам необходимо указать, что ваш класс соответствует такому протоколу, абстрактный метод означает, что вы должны переопределить такой метод.

Другими словами, протоколы являются своего рода необязательными, вам нужно указать базовый класс и протокол, если вы не укажете протокол, вам не нужно переопределять такие методы.

Абстрактный метод означает, что вам нужен базовый класс, но вам необходимо реализовать свой собственный метод или два, что не одно и то же.

Мне нужно такое же поведение, поэтому я искал решение. Думаю, в Swift такой функции нет.


1

Есть еще одна альтернатива этой проблеме, хотя есть и обратная сторона по сравнению с предложением @ jaumard; для этого требуется оператор возврата. Хотя я упускаю из виду необходимость этого, потому что он заключается в непосредственной выдаче исключения:

class AbstractMethodException : NSException {

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

А потом:

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

Все, что будет после этого, недостижимо, поэтому я не понимаю, зачем форсировать возврат.


Вы имеете в виду AbstractMethodException().raise()?
Joshcodes

Эээ ... Наверное. Я не могу сейчас протестировать, но если это сработает, то да
Андре Фрателли

1

Не знаю, будет ли это полезно, но у меня была аналогичная проблема с абстрактным методом при попытке создать игру SpritKit. Мне нужен был абстрактный класс Animal с такими методами, как move (), run () и т.д., но имена спрайтов (и другие функции) должны предоставляться дочерними элементами класса. В итоге я сделал что-то вроде этого (проверено на 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"]
        }
    }
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.