Swift语言中的抽象类

163
有没有一种方法可以在Swift语言中创建抽象类,或者这只是像Objective-C一样的限制?我想创建一个抽象类,类似于Java定义的抽象类。


你需要将整个类都声明为抽象类还是只有其中的一些方法?单个方法和属性的答案请参见此处:http://stackoverflow.com/a/39038828/2435872。在Java中,您可以拥有抽象类,其中没有任何抽象方法。这种特殊功能Swift不提供。 - jboi
11个回答

201

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 属性。
最重要的是,注意没有动态派发。当在存储为 SoftwareEngineer 的实例上调用 logSalary 时,它调用了该方法的覆盖版本。将实例转换为 Employee 后调用 logSalary 时,它调用原始实现(即使实例实际上是 Software Engineer,它也不会动态派发到覆盖的版本)。
要获取更多信息,请查看关于该特性的精彩 WWDC 视频:Building Better Apps with Value Types in Swift

3
protocol Animal { var property: Int { get } }如果不想让属性有 setter,也可以省略掉 set - drewag
4
我认为这个wwdc视频更加相关。 - Mario Zannone
2
@MarioZannone 那个视频让我大开眼界,也让我爱上了Swift。 - Scott H
3
如果您只是在Employee协议声明中添加func logSalary(),则示例将为对logSalary()的两个调用均打印“overridden”。这是在Swift 3.1中实现多态性的好处。在这两种情况下都会调用正确的方法。 - Mike Taverne
1
关于动态分派的规则是这样的...如果方法在扩展中定义,则它是静态分派的。如果它也在您正在扩展的协议中定义,则它是动态分派的。不需要Objective-C运行时。这是纯Swift行为。 - Mark A. Donohoe
显示剩余10条评论

53

请注意,此答案针对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. 编译时检查,确保所有Drivable都实现了speed
  2. 您可以为符合Drivable的所有类型实现默认行为(accelerate
  3. Drivable保证不会被实例化,因为它只是一个协议

这个模型实际上更像是特征(traits),意味着您可以符合多个协议并且采用其中任何一个的默认实现,而使用抽象超类则仅限于简单的类层次结构。


然而,并不总是有可能扩展某些协议,例如UICollectionViewDatasource。我想消除所有样板代码并将其封装在单独的协议/扩展中,然后由多个类重复使用。事实上,在这里模板模式会非常完美,但是... - Richard Topchii
4
在Car类中,你无法覆盖accelerate方法。如果这样做了,在扩展接口Driveable中的实现仍将被调用,而不会有任何编译器警告。这与Java的抽象类非常不同。 - Gerd Castan
@GerdCastan 确实,协议扩展不支持动态分派。 - IluTov

16

最简单的方法是在协议扩展中的抽象方法(不是变量)中调用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()

2
这是一个很好的答案。我没想到如果你调用 (MyConcreteClass() as MyInterface).myMethod() 它会起作用,但它确实可以!关键是在协议声明中包含 myMethod;否则调用会崩溃。 - Mike Taverne

16

我认为这是最接近Java的abstract或C#的abstract的:

class AbstractClass {

    private init() {

    }
}
请注意,为了使“private”修饰符起作用,您必须在单独的Swift文件中定义此类。
编辑:但是,此代码仍然不允许声明抽象方法并因此强制实现它。

4
但是,这并不会强制子类在父类中拥有该函数的基本实现的情况下覆盖该函数。 - Matthew Quiros
在C#中,如果你在一个抽象基类中实现了一个函数,那么你不必强制在它的子类中实现它。 然而,这段代码并不允许你声明一个抽象方法以强制重写。 - Teejay
假设ConcreteClass是AbstractClass的子类。你如何实例化ConcreteClass? - Javier Cadiz
2
ConcreteClass应该有一个公共构造函数。除非它们在同一个文件中,否则AbstractClass可能需要一个受保护的构造函数。据我所记得,Swift中不存在protected访问修饰符。因此,解决方案是在同一个文件中声明ConcreteClass。 - Teejay

13

经过几周的努力,我终于意识到如何将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());
    }
}

然而,我认为苹果没有实现抽象类,因为它通常使用委托+协议模式。例如,上面的相同模式最好这样完成:

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
此外,如果您的示例都是在同一个用例上进行的话,会更有帮助。GoldenSpoonChild 是一个稍微令人困惑的名称,尤其是因为 Mother 似乎是在扩展它。 - Angad
@Angad 委托模式是相同的用例,但它不是一种翻译;它是一种不同的模式,因此必须采取不同的视角。 - Josh Woodcock

11

使用协议可以模拟抽象类的方式,这是一个例子:

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

依然是一个丑陋的解决方案... - undefined

2

另一种实现抽象类的方法是使用块初始化器。我是这样做的:

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
@Cristik,我表达了主要思路,但这不是完整的解决方案。这样,你可以因为它们没有针对你的情况提供足够细节而不喜欢80%的答案。 - Alexey Yarmolovich
1
@AlexeyYarmolovich 谁说我不讨厌80%的答案?:) 开玩笑,我建议你改进一下你的例子,这会帮助其他读者,并通过获得赞同来帮助你。 - Cristik

2

这是一个非常老的问题,但仍然存在... 这是一段实际代码片段,可以在Swift 5.2上编译并按预期工作:

protocol Context {
    init() throws
    func out(_ aStr: String) throws
    // Other stuff
}

class AbstractContext: Context {
    required init() throws {
        if Self.self === AbstractContext.self {
            preconditionFailure("Call to abstract method \(Self.self).\(#function)") 
        }
    }

    func out(_ aStr: String) throws {
        preconditionFailure("Call to abstract method \(Self.self).\(#function)") 
    }

    // Other stuff
}

class CompileContext: AbstractContext {
    required init() throws {}

    override func out(_ aStr: String) throws {
        print(aStr)
    }

    // Other stuff
}

一旦我删除CompileContext.out,我得到的结果如下:

Fatal error: Call to abstract method CompileContext.out(_:): file swiftpg/contexts.swift, line 28

1

由于没有动态分派的限制,您可以像这样做:

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

0
将所有对基类的抽象属性和方法的引用移动到协议扩展实现中,其中Self约束为Base类。这样您就可以访问Base类的所有方法和属性。此外,编译器会检查派生类在协议中的抽象方法和属性的实现情况。
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;
  }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接