【问题标题】:Abstract functions in Swift LanguageSwift 语言中的抽象函数
【发布时间】:2014-07-29 09:47:34
【问题描述】:

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

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

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

【问题讨论】:

  • 这与您的其他question 非常接近,但这里的答案似乎要好一些。
  • 问题相似,但解决方案却大不相同,因为抽象类与抽象函数的用例不同。
  • 是的,为什么我也没有投票关闭,但是除了“你不能”之外,答案不会有用在这里你得到了最好的答案:)

标签: inheritance abstract-class swift


【解决方案1】:

Swift 中没有抽象的概念(如 Objective-C),但你可以这样做:

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

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

【讨论】:

  • 您也可以使用assert(false, "This method must be overriden by the subclass")
  • fatalError("This method must be overridden")
  • 当方法具有返回类型时,断言将需要返回语句。我认为因为这个原因 fatalError() 更好
  • 在 Swift 中还有 preconditionFailure("Bla bla bla"),它将在发布版本中执行,并且还消除了对 return 语句的需要。编辑:刚刚发现这个方法基本上等于fatalError(),但它是更合适的方法(更好的文档,在Swift中引入precondition()assert()assertionFailure(),阅读here
  • 如果函数有返回类型怎么办?
【解决方案2】:

我明白你现在在做什么,我认为你最好使用协议

protocol BaseProtocol {
    func abstractFunction()
}

那么,你只要遵守协议:

class SubClass : BaseProtocol {

    func abstractFunction() {
        // Override
        println("Override")
    }
}

如果您的类也是子类,则协议遵循超类:

class SubClass: SuperClass, ProtocolOne, ProtocolTwo {}

【讨论】:

    【解决方案3】:

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

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

    如果你没有在你的类中提供 abstractFunction 这是一个错误。

    如果您仍然需要基类来处理其他行为,您可以这样做:

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

    【讨论】:

    • 这不太行。如果 BaseClass 想要调用那些空函数,那么它也必须实现协议,然后实现函数。此外,子类仍然没有被强制在编译时实现函数,你也没有强制实现协议。
    • 这就是我的意思.... BaseClass 与协议无关,并且要像抽象类一样工作,它应该与它有关。为了澄清我写的内容“如果 BaseClass 想要调用那些空函数,那么它还必须实现协议,这将迫使你实现这些函数——这会导致子类在编译时不被强制实现这些函数,因为 BaseClass 已经做到了”。
    • 这真的取决于你如何定义“抽象”。抽象函数的全部意义在于你可以把它放在一个可以做普通类事情的类上。例如,我想要这个的原因是因为我需要定义一个静态成员,你似乎无法用协议来做。所以我很难看出这符合抽象函数的有用点。
    • 我认为上面被赞成的 cmets 的担忧在于,这不符合 Liskov 替换原则,该原则指出“程序中的对象应该可以用它们的子类型的实例替换而不改变该程序的正确性。”假设这是一个抽象类型。这在工厂方法模式的情况下尤其重要,其中基类负责创建和存储源自子类的属性。
    • 抽象基类和协议不是一回事。
    【解决方案4】:

    使用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 代替。

    【讨论】:

    • 抽象方法的好处是检查是在编译时完成的。
    • 不要使用断言,因为在归档时,您会收到“预期返回的函数中缺少返回”错误。使用 fatalError("这个方法必须被子类覆盖")
    【解决方案5】:

    这个问题还有另一种选择,尽管与@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() 吗?
    • 错误...可能。我现在无法测试,但如果它可以这样工作,那就是
    【解决方案6】:

    我将相当多的代码从支持抽象基类的平台移植到 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。

    这个方法唯一真正的缺点是,由于基类没有实现协议,如果你有一个需要访问协议属性/方法的基类方法,你将不得不在派生类中覆盖它,并且从那里调用传递self 的基类(通过super),以便基类有一个协议的实例来完成它的工作。

    例如,假设 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 满足了协议的那一部分,但派生类仍然能够以相当直接的方式共享基类逻辑。

    【讨论】:

    • 对“基类不实现协议”有很好的理解和深入的了解......这是抽象类的重点。我对缺少这个基本的 OO 功能感到困惑,但话又说回来,我是在 Java 上长大的。
    • 然而实现不允许无缝实现模板函数方法,其中模板方法调用子类中实现的大量抽象方法。在这种情况下,您应该将它们编写为超类中的普通方法、协议中的方法以及子类中的实现。在实践中,你必须写三遍相同的东西,并且只依靠覆盖检查来确保不会造成任何灾难性的拼写错误!我真的希望现在 Swift 对开发者开放,能够引入成熟的抽象函数。
    • 这么多时间,通过引入“protected”关键字可以节省代码。
    【解决方案7】:

    这样做的一种方法是使用基类中定义的可选闭包,子类可以选择是否实现它。

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

    【讨论】:

    • 我喜欢这种方法的地方是我不必记住在继承类中实现协议。我的 ViewController 有一个基类,这就是我如何强制执行 ViewController 特定的可选功能(例如,应用程序变得活跃等),这些功能可能会被基类功能调用。
    【解决方案8】:

    我不知道它是否有用,但我在尝试构建 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"]
            }
        }
    }
    

    【讨论】:

      【解决方案9】:

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

      在一个协议中你需要指定你的类符合这样的协议,一个抽象方法意味着你必须重写这样的方法。

      也就是说,协议是一种可选项,你需要指定基类和协议,如果你不指定协议,那么你不必重写这些方法。

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

      我需要相同的行为,这就是我寻找解决方案的原因。我猜 Swift 缺少这样的功能。

      【讨论】:

        【解决方案10】:

        好吧,我知道我迟到了,我可能正在利用已经发生的变化。对此感到抱歉。

        无论如何,我想贡献我的答案,因为我喜欢做测试,fatalError() 的解决方案是,AFAIK,不可测试,有异常的解决方案更难测试。

        我建议使用更 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() 时收到的错误或运行时发生的异常。

        【讨论】:

        • 最好的答案。
        • 很好的答案,但是您的基本抽象无法存储属性,但是
        【解决方案11】:

        这似乎是 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 老人)来说,这个问题不时发生。我已经阅读了这篇文章中的所有答案,这一次我认为我找到了一个看起来可行的模式——至少对我来说是这样。
        • 更新:看起来 Apple 的 UIKit 实现者使用相同的模式。 UITableViewController 实现了 UITableViewDelegate,但仍需要通过显式设置 delegate 属性来注册为委托。
        • 这一切都在 Xcode 7.3.1 的 Playground 上进行了测试

        【讨论】:

        • 也许并不完美,因为我在向其他类隐藏类的接口时遇到了问题,但这已经足够我在 Swift 中实现经典的工厂方法了。
        • 嗯。我更喜欢这个答案的外观,但我也不确定它是否合适。即,我有一个 ViewController 可以显示一些不同类型的对象的信息,但是显示该信息的目的是相同的,使用所有相同的方法,但是根据对象以不同的方式提取和组合信息.所以,我有这个 VC 的父委托类,以及每种对象类型的委托的子类。我根据传入的对象实例化一个委托。在一个示例中,每个对象都存储了一些相关的 cmets。
        • 所以我在父委托上有一个getComments 方法。有一些评论类型,我想要与每个对象相关的评论类型。因此,如果我不覆盖该方法,我希望在执行delegate.getComments() 时不编译子类。 VC 只知道它有一个 ParentDelegate 对象,在您的示例中称为 delegateBaseClassX,但 BaseClassX 没有抽象方法。我需要 VC 明确知道它正在使用 SubclassedDelegate
        • 希望我理解你的正确。如果没有,你介意问一个新问题,你可以在哪里添加一些代码?我认为你只需要在“doSomethingWithAbstractMethod”中有一个 if 语句来检查是否设置了委托。
        • 值得一提的是,将 self 分配给委托会创建一个保留循环。也许最好将其声明为弱(这对代表来说通常是一个好主意)
        猜你喜欢
        • 2014-07-29
        • 2018-12-02
        • 1970-01-01
        • 2014-05-28
        • 2012-11-10
        • 1970-01-01
        • 1970-01-01
        • 2018-09-17
        相关资源
        最近更新 更多