【问题标题】:Abstract classes in Swift LanguageSwift 语言中的抽象类
【发布时间】:2014-07-29 09:50:27
【问题描述】:

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

【问题讨论】:

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

标签: inheritance abstract-class subclass swift


【解决方案1】:

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 属性。

最重要的是,请注意没有动态调度。当在存储为SoftwareEngineer 的实例上调用logSalary 时,它会调用该方法的覆盖版本。当实例被转换为Employee 后在实例上调用logSalary 时,它会调用原始实现(即使实例实际上是Software Engineer,它也不会动态分派到覆盖的版本。

欲了解更多信息,请查看有关该功能的精彩 WWDC 视频:Building Better Apps with Value Types in Swift

【讨论】:

  • protocol Animal { var property : Int { get set } }。如果您不希望该属性具有 setter,也可以省略该 set
  • 我认为this wwdc video 更相关
  • @MarioZannone 那个视频让我大吃一惊,让我爱上了 Swift。
  • 如果您只是将 func logSalary() 添加到 Employee 协议声明中,那么对于对 logSalary() 的两个调用,该示例都会打印 overridden。这是在 Swift 3.1 中。因此,您可以获得多态性的好处。在这两种情况下都会调用正确的方法。
  • 动态调度的规则是这样...如果它也在您扩展的协议中定义,那么它是动态调度的。不需要 Objective-C 运行时。这是纯粹的 Swift 行为。
【解决方案2】:

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

这个模型实际上更像特征,这意味着您可以遵守多种协议并采用其中任何一种的默认实现,而使用抽象超类时,您只能使用简单的类层次结构。

【讨论】:

  • 不过,并不总是有可能扩展某些协议,例如UICollectionViewDatasource。我想删除所有样板并将其封装在单独的协议/扩展中,然后由多个类重新使用。事实上,模板模式在这里会很完美,但是......
  • 您不能在 ˚Car˚ 中覆盖 ˚accelerate˚。如果你这样做了,在没有任何编译器警告的情况下仍然会调用 ˚extentsion Driveable˚ 中的实现。与 Java 抽象类非常不同
  • @GerdCastan True,协议扩展不支持动态调度。
【解决方案3】:

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

class AbstractClass {

    private init() {

    }
}

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

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

【讨论】:

  • 不过,这并没有强制子类覆盖函数,同时在父类中也有该函数的基本实现。
  • 在 C# 中,如果您在抽象基类中实现一个函数,则不必在其子类中实现它。不过,此代码不允许您声明抽象方法以强制覆盖。
  • 可以说 ConcreteClass 是 AbstractClass 的子类。你如何实例化 ConcreteClass ?
  • ConcreteClass 应该有一个公共构造函数。您可能需要 AbstractClass 中的受保护构造函数,除非它们在同一个文件中。据我记得,protected 访问修饰符在 Swift 中不存在。所以解决方法是在同一个文件中声明ConcreteClass。
【解决方案4】:

最简单的方法是使用对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;否则通话会崩溃。
【解决方案5】:

经过几周的努力,我终于明白了如何将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 等。这有帮助吗?

【讨论】:

  • 此外,如果您的两个示例都在同一个用例上,这将有所帮助。 GoldenSpoonChild 是一个有点令人困惑的名字,尤其是考虑到母亲似乎在扩展它。
  • @Angad 委托模式是相同的用例,但它不是翻译;这是一个不同的模式,所以它必须采取不同的视角。
【解决方案6】:

有一种方法可以使用协议来模拟抽象类。 这是一个例子:

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

【讨论】:

    【解决方案7】:

    实现抽象类的另一种方法是阻塞初始化程序。 我是这样做的:

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

    【讨论】:

    • 这不提供任何保证和/或检查。在运行时炸毁是执行规则的不好方法。最好将 init 设为私有。
    • 抽象类也应该支持抽象方法。
    • @Cristik 我展示了主要思想,它不是完整的解决方案。这样你可以不喜欢 80% 的答案,因为它们对你的情况不够详细
    • @AlexeyYarmolovich 谁说我不喜欢 80% 的答案? :) 开个玩笑,我建议您的示例可以改进,这将帮助其他读者,并通过投票来帮助您。
    【解决方案8】:

    这是一个非常古老的问题,但仍然......这是在 Swift 5.2 上编译并按预期工作的实际代码的 sn-p:

    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
    

    【讨论】:

      【解决方案9】:

      由于没有动态调度的限制,你可以这样做:

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

      【讨论】:

        【解决方案10】:

        我试图创建一个Weather 抽象类,但使用协议并不理想,因为我不得不一遍又一遍地编写相同的init 方法。扩展协议并编写 init 方法存在问题,特别是因为我使用符合 NSCodingNSObject

        所以我想出了这个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
        }
        

        【讨论】:

          【解决方案11】:

          将所有对 Base 类的抽象属性和方法的引用移动到协议扩展实现,其中 Self 约束到 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;
            }
          }
          

          【讨论】:

            猜你喜欢
            • 2014-07-29
            • 1970-01-01
            • 2019-11-03
            • 1970-01-01
            • 2014-05-28
            • 2018-12-02
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多