Swift语言中的抽象类


140

有没有一种方法可以用Swift语言创建一个抽象类,还是像Objective-C一样有局限性?我想创建一个与Java定义的抽象类相当的抽象类。


您需要整个类是抽象类还是其中的某些方法?有关单个方法和属性,请参见此处的答案。stackoverflow.com/a/39038828/2435872。在Java中,您可以拥有没有抽象方法的抽象类。Swift没有提供该特殊功能。
jboi

Answers:


174

Swift中没有抽象类(就像Objective-C一样)。最好的选择是使用Protocol,就像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存储为的实例上调用when时,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中的版本。因此,您可以获得多态的好处。在两种情况下都将调用正确的方法。
Mike Taverne

1
关于动态分派的规则是...如果在扩展中定义了方法,则将其静态分派。如果您要扩展的协议中也定义了它,那么它将被动态调度。不需要Objective-C运行时。这是纯粹的Swift行为。
Mark A. Donohoe '18

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. 编译时间检查可确保所有 Drivable实现speed
  2. 您可以为所有符合Drivableaccelerate)的
  3. Drivable 保证不会被实例化,因为它只是一个协议

该模型的行为实际上更像是特征,这意味着您可以遵循多种协议并采用其中的任何默认实现,而对于抽象超类,则只能限于简单的类层次结构。


不过,并非总是有可能扩展某些协议,例如UICollectionViewDatasource。我想删除所有样板并将其封装在单独的协议/扩展中,然后由多个类重复使用。实际上,模板模式在这里会是完美的,但是……
理查德·托皮奇

1
您无法在“汽车”中覆盖“加速”。如果这样做,仍会在没有任何编译器警告的情况下调用“扩展驱动器”中的实现。与Java抽象类非常不同
Gerd Castan

@GerdCastan是的,协议扩展不支持动态调度。
IluTov

15

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

class AbstractClass {

    private init() {

    }
}

请注意,为了 private修饰符起作用,必须在单独的Swift文件中定义此类。

编辑:仍然,此代码不允许声明抽象方法,因此强制其实现。


4
尽管如此,这并不强制子类重写函数,同时在父类中也具有该函数的基本实现。
马修·基罗斯

在C#中,如果您在抽象基类中实现一个函数,则无需强制在其子类中实现该函数。尽管如此,此代码仍不允许您声明抽象方法以强制覆盖。
Teejay 2015年

可以说ConcreteClass的子类是AbstractClass。您如何实例化ConcreteClass?
哈维尔·加的斯

2
ConcreteClass应该有一个公共的构造函数。除非它们在同一文件中,否则您可能需要在AbstractClass中使用受保护的构造函数。据我所知,Swift中不存在受保护的访问修饰符。因此解决方案是在同一文件中声明ConcreteClass。
Teejay

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在协议声明中;否则,通话会崩溃。
Mike Taverne

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设为私有。
Морт2015年

抽象类也应该支持抽象方法。
Cristik '16

@Cristik我展示了主要思想,它不是完整的解决方案。这样,您可能不喜欢80%的答案,因为这些答案不够详细,无法满足您的情况
Alexey Yarmolovich 2016年

1
@AlexeyYarmolovich谁说我不讨厌80%的答案?:)开个玩笑,我建议您可以改善您的示例,这将对其他读者有所帮助,并通过获得投票帮助您。
Cristik '16

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

将对基类的抽象属性和方法的所有引用移至协议扩展实现,将自约束移至基类。您将可以访问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;
  }
}

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()
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.