스위프트 언어의 추상 함수


127

신속한 언어로 추상 기능을 만들고 싶습니다. 가능합니까?

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

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

이것은 다른 질문과 거의 비슷 하지만 여기에 대한 답변이 조금 나아 보입니다.
David Berry

질문은 비슷하지만 추상 클래스는 추상 함수와 사용 사례가 다르기 때문에 솔루션이 크게 다릅니다.
kev

그래, 왜 나도 닫습니다 투표를하지 못했지만, 답변이 유용 이상 없을 것 "당신은 할 수 없다"여기 당신은 가능한 최선의 대답 : 도착
데이비드 베리

답변:


198

Objective-C와 같은 Swift에는 추상 개념이 없지만 다음과 같이 할 수 있습니다.

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

5
어설 션에는 메서드에 반환 형식이있는 경우 return 문이 필요합니다. 한 가지 이유로 fatalError ()가 더 낫다고 생각합니다
André Fratelli

6
또한 preconditionFailure("Bla bla bla")Swift에는 릴리스 빌드에서 실행되며 return 문이 필요하지 않습니다. 편집 : 그냥이 방법은 기본적으로 같다는 것을 발견 fatalError()하지만 더 적절한 방법 (더 나은 설명서와 함께 신속한 도입 precondition(), assert()그리고 assertionFailure()읽기, 여기에 )
Kametrixom

2
함수에 반환 유형이 있으면 어떻게됩니까?
LoveMeow

36

당신이 원하는 것은 기본 클래스가 아니라 프로토콜입니다.

protocol MyProtocol {
    func abstractFunction()
}

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

클래스에 abstractFunction을 제공하지 않으면 오류입니다.

다른 행동을위한 기본 클래스가 여전히 필요한 경우 다음을 수행 할 수 있습니다.

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

23
이것은 작동하지 않습니다. BaseClass가 빈 함수를 호출하려면 프로토콜을 구현 한 다음 함수를 구현해야합니다. 또한 서브 클래스는 여전히 컴파일 타임에 함수를 구현하도록 강요되지 않으며 프로토콜을 구현하도록 강요하지 않았습니다.
bandejapaisa

5
그게 내 요점이야 "BaseClass가 빈 함수를 호출하려면 함수를 구현해야하는 프로토콜도 구현해야합니다. 그러면 서브 클래스가 컴파일 타임에 함수를 강제로 구현하지 않습니다. BaseClass가 이미 수행했기 때문입니다. "
bandejapaisa

4
그것은 "추상"을 어떻게 정의 하느냐에 달려 있습니다. 추상 함수의 요점은 정상적인 클래스 작업을 수행 할 수있는 클래스에 넣을 수 있다는 것입니다. 예를 들어, 내가 원하는 이유는 프로토콜로 할 수없는 정적 멤버를 정의해야하기 때문입니다. 그래서 이것이 추상 함수의 유용한 점을 충족시키는 것을 알기가 어렵습니다.
강아지

3
위에서 언급 한 의견에 대한 우려는 이것이 "프로그램의 객체가 해당 프로그램의 정확성을 변경하지 않고 하위 유형의 인스턴스로 대체되어야한다"는 Liskov 대체 원칙에 부합하지 않는다는 것입니다. 이것이 추상적 유형이라고 가정합니다. 이것은 기본 클래스가 하위 클래스에서 시작된 특성을 작성하고 저장하는 팩토리 메소드 패턴의 경우 특히 중요합니다.
David James

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을 구현해야합니다.

이 메소드의 유일한 단점은 기본 클래스가 프로토콜을 구현하지 않기 때문에 프로토콜 특성 / 메소드에 액세스해야하는 기본 클래스 메소드가있는 경우 파생 클래스의 메소드를 대체해야합니다. 기본 클래스가 (수퍼를 통해) 전달 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

2
그러나이 구현에서는 템플릿 메서드가 서브 클래스에 구현 된 많은 추상 메서드를 호출하는 템플릿 함수 메서드를 완벽하게 구현할 수 없습니다. 이 경우 수퍼 클래스의 일반 메소드, 프로토콜의 메소드 및 서브 클래스의 구현으로 모두 작성해야합니다. 실제로 같은 내용을 세 번 작성해야하며 재정의 확인에만 의존하여 치명적인 철자가 틀리지 않도록해야합니다! 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()

출력을 보려면 놀이터를 확인하십시오.

일부 비고

  • 첫째, 많은 답변이 이미 제공되었습니다. 누군가 가이 길을 찾길 바랍니다.
  • 문제는 실제로 다음을 구현하는 패턴을 찾는 것이 었습니다.
    • 클래스는 파생 서브 클래스 중 하나에서 구현되어야하는 메소드를 호출합니다 (재정의 됨).
    • 최선의 경우, 서브 클래스에서 메소드를 대체하지 않으면 컴파일 시간 동안 오류가 발생 합니다.
  • 추상 메소드에 대한 것은 인터페이스 정의와 기본 클래스의 실제 구현의 일부라는 혼합입니다. 둘 다 동시에. 스위프트는 매우 새롭고 명확하게 정의되어 있기 때문에 "아주"개념 (아직)과 같은 편의성이 없습니다.
  • 나 (빈약 한 오래된 자바 사람) 에게이 문제는 때때로 진화합니다. 나는이 게시물의 모든 답변을 읽었으며 이번에는 적어도 나에게 가능한 패턴을 발견했다고 생각합니다.
  • 업데이트 : Apple의 UIKit 구현자가 동일한 패턴을 사용하는 것처럼 보입니다. 속성 을 명시 적으로 설정하여 UITableViewController구현 UITableViewDelegate하지만 여전히 대리자로 등록해야 delegate합니다.
  • 이 모든 것은 Xcode 7.3.1의 놀이터에서 테스트되었습니다.

다른 클래스에서 클래스의 인터페이스를 숨기는 데 문제가 있기 때문에 완벽하지는 않지만 Swift에서 고전적인 Factory Method를 구현하는 데 충분합니다.
Tomasz Nazarenko

흠. 나는이 답변의 모습을 훨씬 더 좋아하지만 그것이 적합한 지 확실하지 않습니다. 즉, 소수의 서로 다른 유형의 객체에 대한 정보를 표시 할 수있는 ViewController가 있지만 해당 정보를 표시하는 목적은 모두 동일한 방법으로 동일하지만 객체에 따라 정보를 다르게 취합하고 결합합니다. . 따라서이 VC의 부모 대리자 클래스와 각 유형의 객체에 대한 해당 대리자의 하위 클래스가 있습니다. 전달 된 객체를 기반으로 델리게이트를 인스턴스화합니다. 한 예에서 각 객체에는 관련 주석이 저장되어 있습니다.
Jake T.

그래서 getComments부모 대리자에 대한 방법 이 있습니다 . 몇 가지 주석 유형이 있으며 각 객체와 관련된 주석 유형을 원합니다. 따라서 delegate.getComments()해당 메서드를 재정의하지 않으면 하위 클래스가 컴파일되지 않기를 원합니다 . VC는 ParentDelegate객체 delegate또는 BaseClassX예제에서 객체 가 있음을 알고 있지만 BaseClassX추상화 된 메소드는 없습니다. 을 사용하고 있음을 구체적으로 알기 위해서는 VC가 필요합니다 SubclassedDelegate.
Jake T.

나는 당신을 올바르게 이해하기를 바랍니다. 그렇지 않은 경우 코드를 추가 할 수있는 새로운 질문을 하시겠습니까? 델리게이트가 설정되어 있는지 여부에 따라 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

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

보관시 u는 '돌아올 것으로 예상되는 함수에서 누락 된 리턴'오류가 발생하므로 assert를 사용하지 마십시오. 사용 fatalError ( "이 메소드는 서브 클래스에 의해 대체되어야합니다")
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

1

그것이 유용할지는 모르겠지만 SpritKit 게임을 빌드하는 동안 추상 메소드와 비슷한 문제가있었습니다. 내가 원했던 것은 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"]
        }
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.