Абстрактні функції в мові Swift


127

Я хотів би створити абстрактну функцію швидкою мовою. Це можливо?

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

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

Це досить близько до вашого іншого питання, але відповідь тут здається трохи кращою.
Девід Беррі

Питання подібні, але рішення набагато відрізняються, оскільки абстрактний клас має інші випадки використання, ніж абстрактні функції.
кев

Так, чому я не проголосував за те, щоб закрити, але відповіді там не будуть корисними, крім "ти не можеш" Тут ти отримав найкращу доступну відповідь :)
Девід Беррі

Відповіді:


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")
Натхан

5
твердження вимагатимуть оператора return, коли метод має тип повернення. Я думаю, що fatalError () краще з цієї причини
André Fratelli

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

Якщо ви не вводите абстрактну функцію у своєму класі, це помилка.

Якщо вам все-таки потрібна база для іншої поведінки, ви можете зробити це:

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

23
Це не зовсім працює. Якщо BaseClass хоче викликати ці порожні функції, то йому також доведеться реалізувати протокол, а потім реалізувати функції. Також підклас все ще не змушений реалізовувати функції під час компіляції, і ви також не змушені реалізовувати протокол.
bandejapaisa

5
Це моя думка .... BaseClass не має нічого спільного з протоколом, і щоб діяти як абстрактний клас, він повинен мати щось із цим. Щоб уточнити те, що я написав: "Якщо BaseClass хоче викликати ці порожні функції, тоді йому також доведеться реалізувати протокол, який змусить вас реалізувати функції - що призводить до того, що підклас не буде змушений реалізовувати функції під час компіляції, тому що BaseClass це вже зробив ".
bandejapaisa

4
Це дійсно залежить від того, як ви визначаєте "абстрактне". Вся суть абстрактної функції полягає в тому, що ви можете поставити її на клас, який може робити звичайні речі класу. Наприклад, причина, яку я хочу цього, полягає в тому, що мені потрібно визначити статичний член, який, здається, не можна робити з протоколами. Тому мені важко бачити, що це відповідає корисній точці абстрактної функції.
Щеня

3
Я вважаю, що стурбованість вищезазначеними коментарями полягає в тому, що це не відповідає принципу заміни Ліскова, який зазначає, що "об'єкти в програмі повинні бути замінені примірниками їх підтипів, не змінюючи правильність цієї програми". Передбачається, що це абстрактний тип. Це особливо важливо у випадку шаблону заводських методів, коли базовий клас відповідає за створення та зберігання властивостей, що походять з підкласу.
Девід Джеймс

2
Абстрактний базовий клас і протокол - це не одне і те ж.
взуття

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.

Єдиним реальним недоліком цього методу є те, що оскільки базовий клас не реалізує протокол, якщо у вас є метод базового класу, який потребує доступу до властивості / методу протоколу, вам доведеться перекрити це у похідному класі, а звідти викликати базовий клас (через супер) передає 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");
    }
}

Тепер спільнийFunction з похідного класу задовольняє ту частину протоколу, але похідний клас все ще може поділяти логіку базового класу досить просто.


4
Велике розуміння та гарна глибина "базового класу не реалізує протокол" ... що є своєрідною точкою абстрактного класу. Мене бентежить, що ця основна функція OO відсутня, але потім, знову, мене підняли на Java.
Дан Розенстарк

2
Однак реалізація не дозволяє безперешкодно реалізувати метод функції шаблону, коли метод шаблону викликає велику кількість абстрактних методів, реалізованих у підкласах. У цьому випадку вам слід записати їх як звичайні методи у суперкласі, так і методи у протоколі та знову як реалізації в підкласі. На практиці вам потрібно написати три рази одні й ті ж речі і покладатися лише на перевірку на переосмислення, щоб бути впевненим у тому, щоб не зробити зловмисних помилок! Я дуже сподіваюся, що зараз Swift відкритий для розробників, буде запроваджена повноцінна абстрактна функція.
Фабріціо Бартоломуччі

1
Стільки часу, і код можна заощадити, ввівши "захищене" ключове слово.
Взуттєвий

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властивість.
  • Це все перевірено на Playground Xcode 7.3.1

Можливо, не ідеально, тому що у мене є проблеми з приховуванням інтерфейсу класу від інших класів, але цього достатньо, що мені потрібно було для впровадження класичного методу Factory в Swift.
Томаш Назаренко

Хммм. Мені подобається зовнішній вигляд цієї відповіді набагато більше, але я також не впевнений, що вона підходить. Тобто, у мене є ViewController, який може відображати інформацію для декількох типів об'єктів, але мета відображення цієї інформації однакова, з однаковими методами, але витягування та зведення інформації по-різному на основі об'єкта . Отже, у мене є клас батьківського делегата для цього VC та підклас цього делегата для кожного типу об’єктів. Я створюю екземпляр делегата на основі переданого об'єкта. В одному прикладі кожен об'єкт зберігає деякі відповідні коментарі.
Джейк Т.

Тож у мене є getCommentsметод батьківського делегата. Існує декілька типів коментарів, і я хочу відповідати кожному об'єкту. Отже, я хочу, щоб підкласи не збиралися, коли я це робити, delegate.getComments()якщо я не перекривав цей метод. VC просто знає, що у нього є ParentDelegateоб'єкт, який називається delegate, або BaseClassXу вашому прикладі, але BaseClassXвін не має абстрагованого методу. Мені потрібен ВК, щоб спеціально знати, що він використовує SubclassedDelegate.
Джейк Т.

Сподіваюся, я вас правильно зрозумів. Якщо ні, то ви не хочете поставити нове запитання, куди ви можете додати якийсь код? Я думаю, що вам просто потрібно мати if-оператор у перевірці "doSomethingWithAb абстрактMethod", якщо встановлений делегат чи ні.
jboi

Варто зазначити, що присвоєння себе делегату створює цикл збереження. Можливо, краще визнати це слабким (що зазвичай є хорошою ідеєю для делегатів)
Енрікоза

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
Я найкраща відповідь.
Апфельсафт

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замість цього.


Користь абстрактних методів полягає в тому, що перевірки робляться в час компіляції.
Фабріціо Бартоломуччі

не використовуйте аргументи, тому що при архівуванні, ви отримаєте помилку "Відсутнє повернення у функції, яка очікується повернення". Використовуйте fatalError ("Цей метод повинен бути перекритий підкласом")
Запорожченко Олександр

2

Я розумію питання і шукав таке саме рішення. Протоколи не такі, як абстрактні методи.

У протоколі вам потрібно вказати, що ваш клас відповідає такому протоколу, абстрактний метод означає, що ви повинні перекрити такий метод.

Іншими словами, протоколи - це необов’язкові види, вам потрібно вказати базовий клас та протокол, якщо ви не вказуєте протокол, тоді вам не доведеться переосмислювати такі методи.

Абстрактний метод означає, що ви бажаєте базового класу, але вам потрібно реалізувати свій власний метод або два, що не те саме.

Мені потрібна така ж поведінка, саме тому я шукав рішення. Я думаю, Свіфт відсутній у такій функції.


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.