Swift语言中的抽象函数


127

我想用快速语言创建一个抽象函数。可能吗?

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

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

这与您的其他问题非常接近,但此处的答案似乎要好一些。
David Berry

问题相似,但解决方案却大不相同,因为抽象类与抽象函数相比具有不同的用例。
kev

是的,为什么我也没有投票决定关闭,但那里的答案除了“您不能”之外没有用。在这里,您可以获得最佳答案:)
David Berry

Answers:


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

20
fatalError("This method must be overridden")
森2014年

5
当方法具有返回类型时,断言将需要一个return语句。我认为fatalError()是更好的原因之一
安德烈FRATELLI

6
preconditionFailure("Bla bla bla")Swift中也有将在发行版本中执行的项目,并且也不需要return语句。编辑:刚刚发现,此方法基本上是相等的,fatalError()但它是更合适的方法(更好的文档,在Swift中与一起引入precondition()assert()并且在此处assertionFailure()阅读)
Kametrixom 2015年

2
如果函数具有返回类型怎么办?
LoveMeow 2015年

36

您想要的不是基类,而是协议。

protocol MyProtocol {
    func abstractFunction()
}

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

如果您在类中不提供abstractFunction,那就是错误。

如果您仍然需要其他行为的基类,则可以执行以下操作:

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

23
这不太有效。如果BaseClass想要调用这些空函数,则还必须实现协议,然后实现这些函数。同样,子类仍然没有被迫在编译时实现功能,并且您也没有被迫实现协议。
bandejapaisa 2014年

5
这就是我的意思。...BaseClass与协议无关,并且像抽象类一样,它应该与它有关。为了澄清我写的内容,“如果BaseClass想要调用这些空函数,那么它还必须实现协议,该协议将迫使您实现这些功能-然后导致子类在编译时不被迫实现这些功能,因为BaseClass已经做到了”。
bandejapaisa 2014年

4
这实际上取决于您如何定义“抽象”。抽象函数的全部要点是,您可以将其放在可以执行常规类操作的类上。例如,我想要这样做的原因是因为我需要定义一个静态成员,而该成员似乎与协议无关。因此,我很难看出这符合抽象函数的有用之处。
小狗

3
我认为上面提到的评论令人担忧的是,这不符合Liskov替换原则,该原则指出“程序中的对象应该可以用其子类型的实例替换,而不会改变该程序的正确性”。假定这是一种抽象类型。这在工厂方法模式的情况下尤其重要,在该方法中,基类负责创建和存储源自子类的属性。
戴维·詹姆斯

2
抽象基类和协议不是一回事。
Shoerob

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。

此方法的唯一真正缺点是,由于基类未实现协议,因此,如果您有一个需要访问协议属性/方法的基类方法,则必须在派生类中重写该方法,然后从那里进行调用基类(通过super)传递,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");
    }
}

现在,派生类的sharedFunction满足了协议的那部分要求,但是派生类仍然能够以合理直接的方式共享基类逻辑。


4
对“基类未实现协议”的深刻理解和深入……这是抽象类的重点。我为缺少基本的OO功能感到困惑,但是我还是在Java上长大的。
Dan Rosenstark 2014年

2
但是,该实现不允许无缝实现Template Function方法,其中template方法调用在子类中实现的大量抽象方法。在这种情况下,您应该将它们既写为超类中的常规方法,也写为协议中的方法,又写为子类中的实现。实际上,您必须编写三遍相同的文章,并且仅依靠覆盖检查来确保不会造成任何灾难性的拼写错误!我真的希望现在Swift对开发人员开放,应该引入成熟的抽象函数。
Fabrizio Bartolomucci

1
这么多的时间,通过引入“ protected”关键字可以节省代码。
Shoerob

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实现者使用相同的模式。UITableViewController实现,UITableViewDelegate但仍然需要通过显式设置delegate属性将其注册为委托。
  • 这一切都在Xcode 7.3.1的Playground上进行了测试

可能并不完美,因为我在将类的接口与其他类隐藏时遇到了问题,但这足以在Swift中实现经典的Factory Method。
Tomasz Nazarenko

嗯 我更喜欢此答案的外观,但我也不确定它是否合适。即,我有一个ViewController可以显示一些不同类型的对象的信息,但是使用所有相同的方法来显示该信息的目的都是相同的,但是根据对象的不同而将信息汇总在一起。因此,我有一个用于此VC的父委托类,以及每种对象类型的该委托的子类。我根据传入的对象实例化一个委托。在一个示例中,每个对象都存储了一些相关的注释。
杰克T.18年

所以我getComments对父委托有一个方法。注释类型很少,我想要与每个对象相关的类型。因此,delegate.getComments()如果不重写该方法,我希望子类在编译时不进行编译。VC仅知道它有一个ParentDelegate名为的对象delegate,或BaseClassX在您的示例中,但BaseClassX没有抽象方法。我需要VC特别知道它正在使用SubclassedDelegate
杰克T.18年

希望我能正确理解你。如果不是,您介意问一个新问题,在哪里可以添加一些代码?我认为您只需要在“ doSomethingWithAbstractMethod”检查中具有if语句即可,无论是否设置了委托。
jboi

值得一提的是,将自己分配给委托人会创建一个保留周期。也许最好将其声明为弱(对于代表通常是一个好主意)
Enricoza,

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
伊莫最好的答案。
Apfelsaft

1
好的答案,但是您的基本抽象不能存储属性,但是
joehinkle11 19/10/25

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

但是,正如史蒂夫·瓦迪乔Steve Waddicor)所提到的,您可能想要一个protocol代替。


抽象方法的好处是检查是在编译时完成的。
Fabrizio Bartolomucci

不要使用assert,因为在归档时,您会收到“在预期返回的函数中缺少回传”错误。使用fatalError(“此方法必须被子类覆盖”)
Zaporozhchenko Oleksandr

2

我了解这个问题,正在寻找相同的解决方案。协议与抽象方法不同。

在协议中,您需要指定您的类符合该协议,抽象方法意味着您必须重写该方法。

换句话说,协议是可选的,您需要指定基类和协议,如果不指定协议,则不必重写此类方法。

抽象方法意味着您想要一个基类,但需要实现自己的一两个方法,这是不一样的。

我需要相同的行为,这就是为什么我一直在寻找解决方案。我想Swift缺少这样的功能。


1

这个问题还有另一种选择,尽管与@jaumard的建议相比仍然有一个缺点。它需要一个return语句。尽管我错过了要求它的意义,因为它在于直接抛出异常:

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,2015年

嗯...大概 我现在不能测试,但如果它的工作原理是这样,比对
安德烈FRATELLI

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