多重継承

ウィキペディアから、無料の百科事典
ナビゲーションにジャンプ 検索にジャンプ

多重継承は、オブジェクトまたはクラスが複数の親オブジェクトまたは親クラスから機能を継承できる、一部のオブジェクト指向コンピュータープログラミング言語の機能です。これは、オブジェクトまたはクラスが1つの特定のオブジェクトまたはクラスからのみ継承できる単一継承とは異なります。

多重継承は長年にわたって物議を醸している問題であり[1] [2]、特定の機能がどの親クラスであるかがあいまいになる可能性がある「菱形継承問題」などの状況では、複雑さとあいまいさが増すことを反対者が指摘しています。複数の親クラスが上記の機能を実装している場合から継承されます。これは、仮想継承の使用など、さまざまな方法で対処できます[3]あいまいさを解消するために、ミックスイン特性など、継承に基づかないオブジェクト構成の代替方法も提案されています。

詳細

オブジェクト指向プログラミング(OOP)では、継承、1つのクラス(子クラス)親クラスをサブクラス化する2つのクラス間の関係を表します。子は親のメソッドと属性を継承し、共有機能を可能にします。たとえば、食べる、繁殖するなどの機能を備えた可変クラスの哺乳類を作成することができます。次に、マウスを追跡するなどの新しい機能を追加しながら、明示的にプログラムすることなくこれらの機能を継承する子クラスCatを定義します。

多重継承により、プログラマーは、 CatがCartoonキャラクターPetMammalから継承し、これらすべてのクラス内の機能にアクセス できるようにするなど、複数の完全に直交する階層を同時に使用できます。

実装

多重継承をサポートする言語には、C ++Common LispCommon Lisp Object System(CLOS)経由)、EuLisp(EuLisp Object System TELOS経由)、CurlDylanEiffelLogtalkObject REXXScalamixinクラスの使用経由)が含まれます。 )、OCamlPerlPOP-11PythonRRaku、およびTcl(8.6から組み込まれている、または以前のバージョンではIncremental Tcl( Incr Tcl )を介して)[4] [5])。

IBM System Object Model(SOM)ランタイムは多重継承をサポートし、SOMを対象とするプログラミング言語は、複数のベースから継承された新しいSOMクラスを実装できます。

SwiftJava2003リビジョン以降のFortranC#Rubyなどの一部のオブジェクト指向言語は、単一継承を実装していますが、プロトコルまたはインターフェイスは、真の多重継承の機能の一部を提供します。

PHPは、トレイトクラスを使用して特定のメソッド実装を継承します。Rubyモジュールを使用して複数のメソッドを継承します。


菱形継承問題

ダイヤモンドクラスの継承図。

菱形継承問題」(「死の死のダイヤモンド」[6]と呼ばれることもあります)は、2つのクラスBとCがAから継承し、クラスDがBとCの両方から継承する場合に発生するあいまいさです。 BとCがオーバーライドし、Dがそれをオーバーライドしない、Aのメソッドの場合、Dはメソッドのどのバージョンを継承しますか:Bのバージョンですか、それともCのバージョンですか?

たとえば、GUI ソフトウェア開発のコンテキストでは、クラスは(外観用)と(機能/入力処理用)のButton両方のクラスから継承する場合があり、クラス両方がクラスから継承する場合があります。ここで、メソッドがオブジェクトに対して呼び出され、クラスにそのようなメソッドがないが、または(または両方)にオーバーライドされたメソッドがある場合、最終的にどのメソッドを呼び出す必要がありますか? RectangleClickableRectangleClickableObjectequalsButtonButtonequalsRectangleClickable

この状況でのクラス継承図の形から、「菱形継承問題」と呼ばれます。この場合、クラスAが上部にあり、BとCの両方がその下に別々にあり、Dは下部で2つを結合して、ひし形を形成します。

緩和策

言語には、継承が繰り返されるこれらの問題に対処するさまざまな方法があります。

  • C ++はデフォルトで各継承パスを個別にたどるので、Dオブジェクトには実際には2つの個別のAオブジェクトが含まれ、のメンバーの使用はA適切に修飾される必要があります。Atoからの継承とtoからB継承の両方に「」のマークが付いている場合(たとえば、「」)、C ++は特別な注意を払って1つのオブジェクトのみを作成し、のメンバーの使用は正しく機能します。仮想継承と非仮想継承が混在している場合、への非仮想継承パスごとに1つの仮想と非仮想が存在します。C ++では、使用する機能が呼び出される親クラスを明示的に指定する必要があります。ACvirtualclass B : virtual public AAAAAAWorker::Human.AgeC ++は、使用するスーパークラスを限定する方法がないため、明示的な繰り返し継承をサポートしていません(つまり、単一の派生リストにクラスが複数回表示される[class Dog:public Animal、Animal])。C ++では、仮想継承メカニズムを介して複数のクラスの単一インスタンスを作成することもできます(つまりWorker::HumanMusician::Human同じオブジェクトを参照します)。
  • Common Lisp CLOSは、妥当なデフォルトの動作とそれをオーバーライドする機能の両方を提供しようとします。デフォルトでは、簡単に言うと、D,B,C,Aクラス定義でBがCの前に記述されている場合、メソッドはでソートされます。最も具体的な引数クラスを持つメソッドが選択されます(D>(B、C)> A); 次に、サブクラス定義で親クラスに名前が付けられている順序(B> C)。ただし、プログラマーは、特定のメソッド解決順序を指定するか、メソッドを組み合わせるためのルールを指定することにより、これをオーバーライドできます。これはメソッドの組み合わせと呼ばれ、完全に制御できます。MOP(メタオブジェクトプロトコル)は、継承、動的ディスパッチを変更する手段も提供します、クラスのインスタンス化、およびシステムの安定性に影響を与えないその他の内部メカニズム。
  • Curlでは、共有として明示的にマークされているクラスのみを繰り返し継承できます。共有クラスは、クラス内の通常のコンストラクターごとに2次コンストラクターを定義する必要があります。共有クラスの状態がサブクラスコンストラクターを介して最初に初期化されるときに通常のコンストラクターが呼び出され、他のすべてのサブクラスに対してセカンダリコンストラクターが呼び出されます。
  • Eiffelでは、祖先の機能はselectおよびrenameディレクティブを使用して明示的に選択されます。これにより、基本クラスの機能をその子孫間で共有したり、それぞれに基本クラスの個別のコピーを提供したりできます。Eiffelを使用すると、祖先クラスから継承された機能を明示的に結合または分離できます。同じ名前と実装の機能を使用すると、Eiffelは自動的に機能を結合します。クラスライターには、継承された機能の名前を変更してそれらを分離するオプションがあります。多重継承は、エッフェル開発で頻繁に発生します。たとえば、広く使用されているデータ構造とアルゴリズムのEiffelBaseライブラリの効果的なクラスのほとんどには、2つ以上の親があります。[7]
  • Goは、コンパイル時の菱形継承問題を防ぎます。構造体がD2つの構造体を埋め込みBC両方にメソッドがありF()、インターフェースを満たす場合、コンパイラーは、が呼び出された場合、またはのインスタンスがタイプの変数に割り当てられた場合にA「あいまいなセレクター」について文句を言いますおよびのメソッドは、またはで明示的に呼び出すことができますD.F()DABCD.B.F()D.C.F()
  • Java 8では、インターフェースにデフォルトのメソッドが導入されています。A,B,Cがインターフェースの場合、それぞれがの抽象メソッドB,C異なる実装を提供し、菱形継承問題を引き起こす可能性があります。どちらのクラスもメソッドを再実装する必要があります(その本体は呼び出しをスーパー実装の1つに転送するだけです)。そうでない場合、あいまいさはコンパイルエラーとして拒否されます。[8] Java 8より前は、Javaは多重継承をサポートしておらず、インターフェースのデフォルトメソッドが利用できなかったため、ダイヤモンド問題のリスクにさらされていませんでした。AD
  • バージョン1.2のJavaFXスクリプトでは、ミックスインを使用して多重継承を行うことができます競合が発生した場合、コンパイラはあいまいな変数または関数を直接使用することを禁止します。継承された各メンバーには、オブジェクトを目的のミックスインにキャストすることで引き続きアクセスできます(individual as Person).printInfo();
  • Kotlinではインターフェイスの多重継承が許可されていますが、Diamond問題のシナリオでは、子クラスが継承の競合を引き起こすメソッドをオーバーライドし、使用する親クラスの実装を指定する必要があります。例えば super<ChosenParentInterface>.someMethod()
  • Logtalkは、インターフェースと実装の多重継承の両方をサポートし、デフォルトの競合解決メカニズムによってマスクアウトされるメソッドの名前変更とアクセスの両方を提供するメソッドエイリアスの宣言を可能にします。
  • OCamlでは、親クラスはクラス定義の本体で個別に指定されます。メソッド(および属性)は同じ順序で継承され、新しく継承された各メソッドが既存のメソッドをオーバーライドします。OCamlは、クラス継承リストの最後に一致する定義を選択して、あいまいな状況で使用するメソッド実装を解決します。デフォルトの動作をオーバーライドするには、メソッド呼び出しを目的のクラス定義で修飾するだけです。
  • Perlは、継承するクラスのリストを順序付きリストとして使用します。コンパイラーは、スーパークラス・リストの深さ優先探索またはクラス階層のC3線形化を使用して、検出した最初のメソッドを使用します。さまざまな拡張機能により、代替のクラス構成スキームが提供されます。継承の順序は、クラスのセマンティクスに影響します。上記のあいまいさでは、クラスBとその祖先はクラスとその祖先の前にチェックされるCため、inのメソッドは。Aを介して継承されBます。これはIoPicolispと共有されます。Perlでは、 C3線形化または他のアルゴリズムを使用するために、mroまたは他のモジュールを使用してこの動作をオーバーライドできます。[9]
  • Pythonの構造はPerlと同じですが、Perlとは異なり、言語の構文に含まれています。継承の順序は、クラスのセマンティクスに影響します。Pythonは、新しいスタイルのクラスの導入時にこれに対処する必要がありました。これらのクラスにはすべて、共通の祖先がありobjectます。Pythonは、 C3線形化(またはメソッド解決順序(MRO))アルゴリズムを使用してクラスのリストを作成します。このアルゴリズムは、2つの制約を適用します。子は親に先行し、クラスが複数のクラスから継承する場合、それらは基本クラスのタプルで指定された順序で保持されます(ただし、この場合、継承グラフの上位の一部のクラスは、下位のクラスに先行する可能性があります。グラフ[10])。したがって、メソッドの解決順序は次のとおりDですB、、、、CA[11]
  • Rubyクラスには親が1つだけありますが、複数のモジュールから継承することもできます。rubyクラス定義が実行され、メソッドの(再)定義により、実行時に既存の定義が隠されます。ランタイムメタプログラミングがない場合、これは右端の深さ優先探索とほぼ同じセマンティクスを持ちます。
  • Scalaでは、トレイトの複数のインスタンス化が可能です。これにより、クラス階層とトレイト階層の区別を追加することで、複数の継承が可能になります。クラスは単一のクラスからのみ継承できますが、必要な数の特性を組み合わせることができます。Scalaは、結果のリストで最後に出現する各モジュールを除くすべてを削除する前に、拡張された「特性」の右優先深さ優先検索を使用してメソッド名を解決します。したがって、解決順序は次のとおりです。[ 、、、、] DCこれは[、、、]Aなります。BADCBA
  • Tclは複数の親クラスを許可します。クラス宣言での指定の順序は、C3線形化アルゴリズムを使用するメンバーの名前解決に影響します。[12]

クラスが1つの基本クラスからのみ派生できる単一の継承のみを許可する言語には、菱形継承問題はありません。この理由は、そのような言語では、メソッドの繰り返しや配置に関係なく、継承チェーンの任意のレベルで最大で1つのメソッドの実装があるためです。通常、これらの言語を使用すると、クラスはJavaのインターフェイスと呼ばれる複数のプロトコルを実装できます。これらのプロトコルはメソッドを定義しますが、具体的な実装を提供しません。この戦略は、ActionScriptC#DJavaNemerleObject PascalObjective-CSmalltalkSwiftPHP[13]これらすべての言語により、クラスは複数のプロトコルを実装できます。

また、エイダ、C#、Java、Object Pascal、Objective-C、Swift、およびPHPでは、インターフェイスの多重継承が可能です(Objective-CおよびSwiftではプロトコルと呼ばれます)。インターフェイスは、動作を実装せずにメソッドシグネチャを指定する抽象基本クラスのようなものです。(バージョン7までのJavaのような「純粋な」インターフェースは、インターフェースでの実装またはインスタンスデータを許可しません。)それでも、複数のインターフェースが同じメソッドシグネチャを宣言する場合でも、そのメソッドが実装(定義)されるとすぐに継承チェーンのどこでも、その上のチェーン(スーパークラス)でのそのメソッドの実装をオーバーライドします。したがって、継承チェーンの任意のレベルで、任意のメソッドの実装は最大で1つになります。したがって、単一継承メソッドの実装では、インターフェースの多重継承があっても菱形継承問題は発生しません。Java 8およびC#8のインターフェイスにデフォルトの実装が導入されたため、ひし形継承問題を生成することは可能ですが、これはコンパイル時のエラーとしてのみ表示されます。

も参照してください

参考文献

  1. ^ カーギル、TA(1991年冬)。「論争:C ++での多重継承に対するケース」。コンピューティングシステム4(1):69–82。
  2. ^ Waldo、Jim(1991年春)。「論争:C ++での多重継承の場合」。コンピューティングシステム4(2):157–171。
  3. ^ Schärli、Nathanael; デュカス、ステファン; Nierstrasz、オスカー; ブラック、アンドリュー。「特性:構成可能な行動単位」(PDF)Web.cecs.pdx.edu 2016年10月21日取得
  4. ^ "incrTcl"blog.tcl.tk。_ 2020年4月14日取得
  5. ^ 「Tclプログラミング言語の紹介」www2.lib.uchicago.edu 2020年4月14日取得
  6. ^ マーティン、ロバートC.(1997-03-09)。「JavaとC ++:重要な比較」(PDF)Objectmentor.com2005-10-24にオリジナル(PDF)からアーカイブされました2016年10月21日取得
  7. ^ 「標準ECMA-367」Ecma-international.org 2016年10月21日取得
  8. ^ 「ラムダの状態」Cr.openjdk.java.net 2016年10月21日取得
  9. ^ "perlobj"perldoc.perl.org 2016年10月21日取得
  10. ^ 要約。「Python2.3メソッドの解決順序」Python.org 2016年10月21日取得
  11. ^ 「Python2.2で型とクラスを統合する」Python.org 2016年10月21日取得
  12. ^ 「クラスのマンページ」Tcl.tk。 _ 1999-11-16 2016年10月21日取得
  13. ^ 「オブジェクトインターフェイス-手動」PHP.net2007-07-04 2016年10月21日取得

さらに読む

外部リンク