Абстрактные классы на Swift Language


142

Есть ли способ создать абстрактный класс в Swift Language, или это ограничение, как в Objective-C? Я хотел бы создать абстрактный класс, сопоставимый с тем, что Java определяет как абстрактный класс.


Вам нужно, чтобы весь класс был абстрактным или только несколько методов в нем? См. Ответ здесь для отдельных методов и свойств. stackoverflow.com/a/39038828/2435872 . В Java вы можете иметь абстрактные классы, у которых нет ни одного абстрактного метода. Эта особенность не предусмотрена Swift.
jboi

Ответы:


175

В Swift нет абстрактных классов (как и в Objective-C). Лучше всего будет использовать протокол , который похож на интерфейс Java.

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

Примером этой техники может быть:

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

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

Также обратите внимание, что каждый класс или структура, реализующие протокол Employee, должны будут снова объявить свойство AnnualSalary.

Самое главное, обратите внимание, что нет динамической отправки . Когда logSalaryвызывается в экземпляре, который хранится как объект, SoftwareEngineerон вызывает переопределенную версию метода. Когда logSalaryвызывается в экземпляре после того, как он был приведен к типу Employee, он вызывает исходную реализацию (она не отправляет динамически переопределенной версии, даже если экземпляр фактически является Software Engineer.

Для получения дополнительной информации посмотрите отличное видео WWDC об этой функции: Создание лучших приложений с типами значений в Swift


3
protocol Animal { var property : Int { get set } }. Вы также можете не
указывать

3
Я думаю, что это видео wwdc еще более актуально
Марио Занноне

2
@MarioZannone, это видео просто поразило меня и заставило меня влюбиться в Swift.
Scott H

3
Если вы просто добавите func logSalary()в объявление протокола Employee, пример будет напечатан overriddenдля обоих вызовов logSalary(). Это в Swift 3.1. Таким образом, вы получаете преимущества полиморфизма. В обоих случаях вызывается правильный метод.
Майк Таверн,

1
Правило динамической отправки таково ... если метод определен только в расширении, он отправляется статически. Если он также определен в протоколе, который вы расширяете, он отправляется динамически. Нет необходимости в средах выполнения Objective-C. Это чистое поведение Swift.
Марк А. Донохо

47

Обратите внимание, что этот ответ ориентирован на Swift 2.0 и выше.

Вы можете добиться того же поведения с протоколами и расширениями протоколов.

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

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

Затем вы можете добавить поведение по умолчанию для всех типов, которые ему соответствуют.

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

Теперь вы можете создавать новые типы, реализуя Drivable.

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

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

Итак, в основном вы получаете:

  1. Compile проверка времени , что гарантирует , что все Drivableс реализациейspeed
  2. Вы можете реализовать поведение по умолчанию для всех типов, которые соответствуют Drivable( accelerate)
  3. Drivable гарантированно не будет создан, поскольку это просто протокол

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


Однако не всегда есть возможность расширить некоторые протоколы, например UICollectionViewDatasource,. Я хотел бы удалить весь шаблон и инкапсулировать его в отдельный протокол / расширение, а затем повторно использовать несколькими классами. На самом деле, шаблонный узор здесь был бы идеальным, но ...
Ричард Топчи

1
Вы не можете перезаписать «Ускорение» в «Автомобиль». Если вы это сделаете, реализация в «extentsion Driveable» по-прежнему будет вызываться без предупреждения компилятора. Очень непохож на абстрактный класс Java
Герд Кастан

@GerdCastan Верно, расширения протокола не поддерживают динамическую отправку.
IluTov

15

Я думаю, что это ближе всего к Java abstractили C # abstract:

class AbstractClass {

    private init() {

    }
}

Обратите внимание, что для того private, чтобы модификаторы работали, вы должны определить этот класс в отдельном файле Swift.

РЕДАКТИРОВАТЬ: Тем не менее, этот код не позволяет объявить абстрактный метод и, следовательно, принудительно реализовать его.


4
Тем не менее, это не заставляет подкласс переопределять функцию, имея при этом базовую реализацию этой функции в родительском классе.
MLQ

В C #, если вы реализуете функцию в абстрактном базовом классе, вы не обязаны реализовывать ее в его подклассах. Тем не менее, этот код не позволяет вам объявлять абстрактный метод для принудительного переопределения.
Teejay,

Допустим, этот подкласс ConcreteClass, что AbstractClass. Как создать экземпляр ConcreteClass?
Хавьер Кадис

2
ConcreteClass должен иметь открытый конструктор. Вам, вероятно, понадобится защищенный конструктор в AbstractClass, если они не находятся в одном файле. Насколько я помню, в Swift нет модификатора защищенного доступа. Итак, решение состоит в том, чтобы объявить ConcreteClass в том же файле.
Teejay 08

13

Самый простой способ - использовать вызов fatalError("Not Implemented")абстрактного метода (не переменной) в расширении протокола.

protocol MyInterface {
    func myMethod() -> String
}


extension MyInterface {

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

}

class MyConcreteClass: MyInterface {

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

}

MyConcreteClass().myMethod()

Это отличный ответ. Я не думал, что это сработает, если ты позвонишь, (MyConcreteClass() as MyInterface).myMethod()но это работает! Ключ включен myMethodв декларацию протокола; в противном случае вызов вылетает.
Майк Таверн,

11

После нескольких недель борьбы я наконец понял, как перевести абстрактный класс Java / PHP в 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());
    }
}

Однако я думаю, что Apple не реализовала абстрактные классы, потому что вместо этого обычно использует шаблон делегат + протокол. Например, тот же шаблон выше лучше было бы сделать так:

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

Мне нужен такой шаблон, потому что я хотел объединить некоторые методы в UITableViewController, такие как viewWillAppear и т. Д. Было ли это полезно?


1
+1 Планировал применить тот же подход, который вы упомянули первым; интересный указатель на схему делегирования.
Angad

Кроме того, было бы полезно, если бы оба ваших примера были в одном варианте использования. GoldenSpoonChild - это немного сбивающее с толку имя, особенно с учетом того, что Мать, кажется, расширяет его.
Angad

@Angad Шаблон делегата - это тот же вариант использования, но это не перевод; это другой образец, поэтому он должен рассматриваться с другой точки зрения.
Джош Вудкок

8

Есть способ моделирования абстрактных классов с помощью протоколов. Это пример:

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

Еще один способ реализации абстрактного класса - заблокировать инициализатор. Я сделал это так:

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
Это не дает никаких гарантий и / или проверок. Взрыв во время выполнения - плохой способ обеспечить соблюдение правил. Лучше, чтобы init был приватным.
Морт

Абстрактные классы также должны иметь поддержку абстрактных методов.
Cristik 06

@Cristik Я показал основную идею, это не полное решение. Таким образом, вы можете не любить 80% ответов, потому что они недостаточно подробны для вашей ситуации
Алексей Ярмолович

1
@AlexeyYarmolovich, кто сказал, что мне не нравятся 80% ответов? :) Шутя в сторону, я предлагал улучшить ваш пример, это поможет другим читателям и поможет вам, получив положительные голоса.
Cristik

0

Я пытался создать Weatherабстрактный класс, но использование протоколов не было идеальным, поскольку мне приходилось писать одни initи те же методы снова и снова. У расширения протокола и написания initметода были проблемы, особенно с учетом того, что я использовал NSObjectсоответствие NSCoding.

Итак, я придумал это для NSCodingсоответствия:

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

Что касается init:

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

0

Переместите все ссылки на абстрактные свойства и методы базового класса в реализацию расширения протокола, где собственное ограничение - в базовый класс. Вы получите доступ ко всем методам и свойствам Базового класса. Дополнительно компилятор проверяет реализацию абстрактных методов и свойств в протоколе для производных классов

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

С ограничением динамической отправки вы можете сделать что-то вроде этого:

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()
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.