Swift语言中的抽象函数

148

我想在Swift语言中创建一个抽象函数。这是否可行?

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

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

这与您的另一个问题非常接近(https://dev59.com/BWAf5IYBdhLWcg3w9Wmu),但这里的答案似乎更好。 - David Berry
这些问题可能相似,但解决方案却大不相同,因为抽象类与抽象函数有着不同的用例。 - kev
是的,我为什么没有投票关闭呢,但那里的答案除了“你不能”之外就没有用处了。在这里,您得到了最好的可用答案 :) - David Berry
已解决。请参考此链接:https://stackoverflow.com/a/65816990/294884 - undefined
11个回答

231

Swift 中没有抽象的概念(与 Objective-C 不同),但你可以像这样实现:

class BaseClass {
    func abstractFunction() {
        preconditionFailure("This method must be overridden") 
    } 
}

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

13
另外,您可以使用 assert(false, "该方法必须由子类重写") - Erik
24
fatalError("此方法必须被覆盖重写") - nathan
6
当一个方法具有返回类型时,断言将需要一个返回语句。我认为针对这个问题,使用fatalError()更好。 - André Fratelli
9
另外在Swift里还有preconditionFailure("Bla bla bla"),它会在发布版本中执行,并且也消除了需要使用return语句的必要。编辑:刚刚发现这个方法基本上等同于fatalError(),但这是更为适当的方式(更好的文档说明,与precondition()assert()assertionFailure()一起引入到Swift中,阅读此处)。 - Kametrixom
2
如果函数有返回类型怎么办? - LoveMeow
显示剩余6条评论

38

你想要的不是一个基类,而是一个协议。

protocol MyProtocol {
    func abstractFunction()
}

class MyClass : MyProtocol {
    func abstractFunction() {
    }
}
如果在你的类中没有提供abstractFunction,那么就会出现错误。 如果你仍然需要基类来实现其他行为,可以这样做:
class MyClass : BaseClass, MyProtocol {
    func abstractFunction() {
    }
}

31
这段代码并不完全有效。如果BaseClass想调用那些空函数,那么它也必须实现协议并实现这些函数。此外,子类在编译时仍然不被强制实现这些函数,而你也无需强制实现该协议。 - bandejapaisa
8
这是我的观点......BaseClass与协议无关,如果想像抽象类一样运作,它应该与协议有所关联。为了澄清我所说的,“如果BaseClass想要调用那些空函数,那么它也必须实现协议,这会强制你去实现这些函数 - 这导致在编译时Subclass不需要强制实现这些函数,因为BaseClass已经完成了。” - bandejapaisa
4
这取决于您如何定义“抽象”。抽象函数的整个重点在于,您可以将其放在可以执行普通类操作的类上。例如,我需要定义一个静态成员,但是似乎无法使用协议来实现,所以这是我需要的原因。因此,很难看出这符合抽象函数的有用点。 - Puppy
4
以上所赞成的评论引起了人们的关注,并认为这不符合Liskov替换原则,即“程序中的对象应该可以被它们的子类型的实例替换而不改变程序的正确性。” 据推测,这是一个抽象类型。在Factory Method模式中尤为重要,因为基类负责创建和存储源自子类的属性。 - David James
2
一个抽象基类和协议并不是同一回事。 - Shoerob
显示剩余5条评论

32

这似乎是 Apple 在 UIKit 中处理抽象方法的“官方”方式。 看一下 UITableViewController 以及它与 UITableViewDelegate 的交互方式。你需要做的其中一件事就是添加一行:delegate = self。好吧,那正是这个技巧。

protocol AbstractMethodsForClassX {
    func abstractMethod() -> String
}

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

/// 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程序员),这个问题不时会出现。我已经阅读了本帖子中的所有答案,这次我认为我找到了一个看起来可行的模式-至少对我来说是这样的。
  • 更新:看起来Apple的UIKit实现者使用相同的模式。 UITableViewController实现UITableViewDelegate,但仍然需要通过显式地设置delegate属性来注册委托。
  • 所有这些都在Xcode 7.3.1的Playground上测试过。

也许不是完美的,因为我有问题将类的接口隐藏在其他类中,但这已经足够让我在Swift中实现经典的工厂方法。 - Tomasz Nazarenko
嗯,我更喜欢这个答案的外观,但我也不确定它是否合适。即,我有一个ViewController,可以显示少量不同类型对象的信息,但显示该信息的目的相同,具有相同的方法,但根据对象不同以不同的方式提取和组合信息。因此,我为此VC拥有一个父代理类,以及每种类型对象的代理子类。我基于传入的对象实例化一个代理。在一个示例中,每个对象都存储一些相关评论。 - Jake T.
所以我在父代理上有一个getComments方法。有一些评论类型,我想要与每个对象相关的那些评论。因此,如果我没有覆盖该方法,我希望子类在执行delegate.getComments()时不会编译。VC只知道它有一个名为delegate或在您的示例中为BaseClassXParentDelegate对象,但是BaseClassX没有抽象化的方法。我需要VC明确知道它正在使用SubclassedDelegate - Jake T.
我希望我理解的是正确的。如果不是,请问您是否介意提出一个新问题,其中可以添加一些代码?我认为您只需要在“doSomethingWithAbstractMethod”中使用if语句来检查委托是否已设置。 - jboi
值得一提的是,将self分配给代理会创建一个保留循环。也许将其声明为弱引用会更好(通常对于代理来说是个好主意)。 - Enricoza

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
对于“基类没有实现该协议”这一点,您对其有很好的理解和深入的了解……这也是抽象类存在的目的。我很惊讶这种基本的面向对象特性居然会缺失,不过话说回来,我被Java培养出来的。 - Dan Rosenstark
2
然而,该实现不允许无缝实现模板函数方法,其中模板方法调用在子类中实现的大量抽象方法。在这种情况下,应将它们编写为超类中的普通方法,作为协议中的方法,并再次作为子类中的实现。在实践中,您必须三次编写相同的内容,并仅依赖于重写检查以确保不会发生任何灾难性的拼写错误!我真的希望现在Swift向开发人员开放,全面的抽象函数将被引入。 - Fabrizio Bartolomucci
1
通过引入“protected”关键字,可以节省很多时间和代码。 - Shoerob

7

一个做法是使用在基类中定义的可选闭包,子类可以选择实现它或不实现。

class BaseClass {
    var abstractClosure?:(()->())?
    func someFunc()
    {
        if let abstractClosure=abstractClosure
        {
            abstractClosure()
        }
    } 
}

class SubClass : BaseClass {
    init()
    {
        super.init()
        abstractClosure={ ..... }
    }
}

我喜欢这种方法的原因是我不必记得在继承类中实现协议。我有一个用于我的ViewControllers的基类,这就是我如何强制执行ViewController特定的可选功能(例如应用程序变为活动状态等),这些功能可能会被基类功能调用。 - ProgrammierTier

7

嗯,我知道我来晚了,可能会利用已经发生的变化。对此感到抱歉。

无论如何,我想贡献我的答案,因为我喜欢测试和使用 fatalError() 的解决方案在我所知道的情况下是无法被测试的,而带有异常的解决方案则更难以测试。

我建议使用更加Swifty的方法。您的目标是定义一个有一些共同细节但没有完全定义的抽象方法,即抽象方法。 使用协议定义抽象中预期的所有方法,既包括已定义的方法,也包括未定义的方法。然后创建一个协议扩展来实现您的情况下定义的方法。 最后,任何派生类必须实现协议,这意味着所有方法都必须实现,但协议扩展中已经有其实现的方法除外。

下面是一个具体函数的示例:

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

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("This method must be overriden by the subclass")。 - Zaporozhchenko Oleksandr

2
我理解问题并正在寻找相同的解决方案。协议和抽象方法不是同一概念。
在协议中,您需要指定您的类符合该协议,而抽象方法意味着您必须重写该方法。
换句话说,协议有点像可选项,您需要指定基类和协议,如果您没有指定协议,则无需覆盖此类方法。
抽象方法意味着您需要一个基类,但需要实现自己的一个或两个方法,这并不相同。
我需要相同的行为,这就是为什么我在寻找解决方案。我猜Swift缺少这样的功能。

1

这个问题还有另一个解决方案,虽然与@jaumard的提议相比仍有不足之处;它需要一个返回语句。尽管我不明白为什么需要它,因为它直接抛出异常:

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
错误...可能吧。我现在无法测试,但如果它能够这样工作,那么是的。 - André Fratelli

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