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 2014年

うん、なぜ「あなたはできません」ここでは、利用可能な最善の答えを得た:)私はどちらか閉じるには投票しなかったが、答えは便利超えた存在ではありません
デビッド・ベリー

回答:


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ステートメントが必要です。私は致命()は、この一つの理由のために優れていると思う
アンドレあるFratelli

6
またpreconditionFailure("Bla bla bla")、リリースビルドで実行され、returnステートメントの必要性を排除するSwiftもあります。編集:この方法は基本的に等しいfatalError()が、より適切な方法であることがわかりました(より優れたドキュメント、Swiftとともに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 14

5
それが私のポイントです。...BaseClassはプロトコルとは何の関係もありません。抽象クラスのように機能するには、それと何か関係があるはずです。私が書いた内容を明確にするために、「BaseClassがこれらの空の関数を呼び出したい場合、関数の実装を強制するプロトコルも実装する必要があります。これにより、サブクラスがコンパイル時に関数を実装するよう強制されなくなります。 BaseClassはすでにそれを行っているからです。」
bandejapaisa 14

4
それは、「抽象」をどのように定義するかによります。抽象関数の要点は、通常のクラスのことを実行できるクラスに置くことができるということです。たとえば、これが必要な理由は、プロトコルでは実行できない静的メンバーを定義する必要があるためです。そのため、これが抽象関数の有用な点を満たしていることを理解するのは困難です。
パピー

3
上記の賛成意見の懸念は、これが「プログラム内のオブジェクトは、そのプログラムの正確さを変更せずにサブタイプのインスタンスで置き換え可能でなければならない」と述べているリスコフ置換の原則に準拠していないことだと思います。これは抽象型であると想定されています。これは、基本クラスがサブクラスに由来するプロパティの作成と保存を担当するファクトリメソッドパターンの場合に特に重要です。
デビッドジェームズ

2
抽象基本クラスとプロトコルは同じものではありません。
Shoerob 2017年

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
しかし、この実装では、テンプレートメソッドがサブクラスに実装されている多数の抽象メソッドを呼び出すテンプレート関数メソッドをシームレスに実装することはできません。この場合、それらをスーパークラスの通常のメソッドとして、プロトコルのメソッドとして、そしてサブクラスの実装として、両方とも書く必要があります。実際には、同じものを3回記述し、オーバーライドチェックに依存して、悲惨なスペルミスが発生しないことを確認する必要があります。Swiftが開発者に開放されていることを、私は本当に望んでいます。本格的な抽象関数が導入されるでしょう。
Fabrizio Bartolomucci

1
「保護された」キーワードを導入することで、コードを大幅に節約できます。
Shoerob 2017年

23

これは、AppleがUIKitで抽象メソッドを処理する「公式の」方法のようです。UITableViewControllerとそれがどのように機能するかを見てくださいUITableViewDelegate。最初に行うことの1つは、行を追加することです。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で出力を確認してください。

いくつかの備考

  • まず、多くの回答が既に出されています。私は誰かがこれまでずっと見つけてくれることを望みます。
  • 問題は実際に、実装するパターンを見つけることでした:
    • クラスがメソッドを呼び出す。メソッドはその派生サブクラスの1つに実装する必要がある(オーバーライドされる)
    • 最良の場合、メソッドがサブクラスでオーバーライドされない場合、コンパイル時にエラーが発生します
  • 抽象メソッドについてのことは、それらがインターフェイス定義と基本クラスの実際の実装の一部の混合であることです。両方同時に。Swiftは非常に新しく、明確に定義されているため、そのような利便性はありませんが、(まだ)不明瞭な概念があります。
  • 私(貧しい古いJavaの人)にとって、この問題は時々発生します。私はこの投稿ですべての回答を読みましたが、今回は、少なくとも私にとっては、実現可能なパターンを見つけたと思います。
  • 更新:AppleのUIKit実装者が同じパターンを使用しているようです。UITableViewController実装しますUITableViewDelegateが、明示的にdelegateプロパティを設定してデリゲートとして登録する必要があります。
  • これはすべてXcode 7.3.1のPlaygroundでテストされています

他のクラスからクラスのインターフェースを隠すのに問題があるので、おそらく完璧ではないかもしれませんが、これはSwiftでクラシックファクトリメソッドを実装するのに必要なもので十分です。
Tomasz Nazarenko 2017年

うーん。この回答の見た目はもっと好きですが、適切かどうかもわかりません。つまり、私は少数の異なるタイプのオブジェクトの情報を表示できるViewControllerを持っていますが、その情報を表示する目的はすべて同じですが、オブジェクトに基づいて異なる方法で情報を引き出してまとめます。したがって、このVCの親デリゲートクラスと、オブジェクトの種類ごとにそのデリゲートのサブクラスがあります。渡されたオブジェクトに基づいてデリゲートをインスタンス化します。1つの例では、各オブジェクトに関連するコメントが格納されています。
Jake T.

だから私はgetComments親デリゲートにメソッドがあります。コメントの種類はいくつかありますが、各オブジェクトに関連するものが必要です。そのため、delegate.getComments()そのメソッドをオーバーライドしない場合は、サブクラスをコンパイルしないようにします。VCはParentDelegateと呼ばれるオブジェクトdelegate、またはBaseClassXあなたの例ではそれがあることを知っていますBaseClassXが、抽象化されたメソッドはありません。VCがを使用していることを具体的に知る必要がありSubclassedDelegateます。
ジェイク

私はあなたを正しく理解できれば幸いです。そうでない場合は、コードを追加できる新しい質問をしてもよろしいですか?デリゲートが設定されているかどうかにかかわらず、「doSomethingWithAbstractMethod」チェックにifステートメントが必要なだけだと思います。
jboi 2018年

デリゲートに自分自身を割り当てると、保持サイクルが作成されることに言及する価値があります。たぶんそれを弱いものとして宣言する方が良いでしょう(これは一般的にデリゲートにとって良い考えです)
Enricoza

7

これを行う1つの方法は、基本クラスで定義されたオプションのクロージャーを使用することであり、子はそれを実装するかどうかを選択できます。

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

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

このアプローチで気に入っているのは、継承クラスにプロトコルを実装することを覚えておく必要がないことです。私はViewControllersの基本クラスを持っています。これが、基本クラスの機能によって呼び出される可能性のあるViewController固有のオプション機能(アプリがアクティブになるなど)を適用する方法です。
ProgrammierTier

6

ええと、私はゲームに遅れており、起こった変化を利用しているかもしれないことを知っています。申し訳ありません。

いずれにせよ、私は私の答えを提供したいと思います。なぜなら、私はテストを行うのが大好きでfatalError()あり、AFAIK を使用したソリューションはテスト不可能であり、例外を伴うものはテストがはるかに難しいからです。

もっと使用することをお勧めします 迅速なアプローチ。あなたの目標は、いくつかの共通の詳細を持つ抽象化を定義することですが、それは完全には定義されていません。つまり、抽象メソッドです。抽象化で予想されるすべてのメソッドを定義するプロトコルを使用します。定義されたものと定義されていないものの両方を使用します。次に、ケースで定義されているメソッドを実装するプロトコル拡張を作成します。最後に、すべての派生クラスはプロトコルを実装する必要があります。つまり、すべてのメソッドを意味しますが、プロトコル拡張の一部であるものはすでに実装されています。

例を1つの具象関数で拡張する:

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 2018

1
良い回答ですが、基本的な抽象化ではプロパティを保存できません
joehinkle11 '25 / 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代わりにaが必要です。


抽象メソッドの利点は、チェックがコンパイル時に行われることです。
Fabrizio Bartolomucci

アサートは使用しないでください。アーカイブすると、「返されるはずの関数に戻り値がありません」というエラーが発生するためです。fatalError( "このメソッドはサブクラスでオーバーライドする必要があります")を使用してください
Zaporozhchenko Oleksandr

2

私は質問を理解し、同じ解決策を探していました。プロトコルは抽象メソッドと同じではありません。

プロトコルでは、クラスがそのようなプロトコルに準拠していることを指定する必要があります。抽象メソッドは、そのようなメソッドをオーバーライドする必要があることを意味します。

言い換えると、プロトコルはオプションの一種であり、基本クラスとプロトコルを指定する必要があります。プロトコルを指定しない場合、そのようなメソッドをオーバーライドする必要はありません。

抽象メソッドは、基本クラスが必要だが、独自のメソッドを1つまたは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ゲームをビルドしようとしたときに、抽象化されたメソッドで同様の問題が発生しました。私が欲しかったのは、move()、run()などのメソッドを持つ抽象Animalクラスですが、スプライト名(およびその他の機能)はクラスの子によって提供される必要があります。だから私はこのようなことをしました(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.