継承(オブジェクト指向プログラミング)

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

オブジェクト指向プログラミングでは継承は、オブジェクトまたはクラスを別のオブジェクト(プロトタイプベースの継承)またはクラス(クラスベースの継承)に基づいて、同様の実装を保持するメカニズムですまた、スーパークラスやベースクラスなどの既存のクラスから新しいクラス(サブクラス)を派生させ、それらをクラスの階層に形成することとしても定義されます。ほとんどのクラスベースのオブジェクト指向言語では、継承によって作成されたオブジェクトである「子オブジェクト」は、コンストラクタ、デストラクタ、を除いて、「親オブジェクト」のすべてのプロパティと動作を取得します。基本クラスのオーバーロードされた演算子フレンド関数。継承により、プログラマーは既存のクラスに基づいて構築されたクラスを作成し[1]、同じ動作を維持しながら新しい実装を指定し(インターフェイスを実現)、コードを再利用し、パブリッククラスとインターフェイスを介して元のソフトウェアを独立して拡張できます。継承によるオブジェクトまたはクラスの関係は、有向非巡回グラフを生成します。

継承は1969年にSimula [2]のために発明され、現在ではJavaC ++PHPPythonなどの多くのオブジェクト指向プログラミング言語で使用されています

継承されたクラスは、その親クラスまたはスーパークラスのサブクラスと呼ばれます。「継承」という用語は、クラスベースプログラミングとプロトタイプベースプログラミングの両方で大まかに使用されますが、狭義には、クラスベースプログラミング(あるクラスが別のクラスから継承する)に使用され、プロトタイプベースプログラミングの対応する手法は次のとおりです。代わりに委任と呼ばれます(1つのオブジェクトが別のオブジェクトに委任します)。

継承をサブタイピングと混同しないでください[3] [4]継承とサブタイピングが一致する言語もあれば、[a]異なる言語もあります。一般に、サブタイピングはis-a関係を確立しますが、継承は実装を再利用するだけで構文関係を確立し、必ずしもセマンティック関係を確立するわけではありません(継承は動作のサブタイピングを保証しません)。これらの概念を区別するために、サブタイピングはインターフェイス継承と呼ばれることもあります(型変数の特殊化によってサブタイピング関係も誘導されることを認めない場合)が、ここで定義されている継承は実装継承またはコード継承と呼ばれます。[5]それでも、継承はサブタイプの関係を確立するために一般的に使用されるメカニズムです。[6]

継承は、あるオブジェクトに別のオブジェクトが含まれる(または、あるクラスのオブジェクトに別のクラスのオブジェクトが含まれる)オブジェクトコンポジションとは対照です継承よりも構成を参照してくださいコンポジションは、サブタイピングのis-a関係とは対照的に、 has-a関係を実装します。

タイプ

単一継承
多重継承

パラダイムと特定の言語に基づいて、さまざまなタイプの継承があります。[7]

単一継承
ここで、サブクラスは1つのスーパークラスの機能を継承します。クラスは別のクラスのプロパティを取得します。
多重継承
ここで、1つのクラスが複数のスーパークラスを持つことができ、すべての親クラスから機能を継承できます。

「多重継承 ...は、効率的に実装するのが非常に難しいと広く考えられていました。たとえば、ObjectiveCに関する彼の本のC ++の要約で、Brad Coxは、C ++に多重継承を追加することは不可能であると実際に主張しました。 1982年に多重継承を検討し、1984年にシンプルで効率的な実装手法を見つけたので、この課題に抵抗できませんでした。これが、ファッションが一連のイベントに影響を与えた唯一のケースであると思われます。 。」[8]

マルチレベルの継承
サブクラスが別のサブクラスから継承されている場合。「マルチレベル継承」の図に示すように、クラスが別の派生クラスから派生することは珍しくありません。
マルチレベルの継承
クラスAは、派生クラスBの基本クラスとして機能し、派生クラス Bは、派生クラスCの基本クラスとして機能しますクラスBは、 ACの間の継承へのリンクを提供するため、中間基本クラスとして知られていますチェーンABCは継承パスとして知られています
マルチレベルの継承を持つ派生クラスは、次のように宣言されます。
クラスA (...); //基本クラスクラスB public A (...); // AクラスCから派生したB public B (...); // Bから派生したC       
       
       
このプロセスは、任意の数のレベルに拡張できます。
階層継承
これは、1つのクラスが複数のサブクラスのスーパークラス(基本クラス)として機能する場所です。たとえば、親クラスAは、2つのサブクラスBとCを持つことができます。BとCの親クラスは両方ともAですが、BとCは2つの別個のサブクラスです。
ハイブリッド継承
ハイブリッド継承とは、上記の2つ以上のタイプの継承が混在する場合です。この例は、クラスAに2つのサブクラスCとDを持つサブクラスBがある場合です。これは、マルチレベル継承と階層継承の両方の混合です。

サブクラスとスーパークラス

サブクラス派生クラス継承クラスまたは子クラスは、1つ以上の他のクラス(スーパークラス基本クラス、または親クラスと呼ばれる)から1つ以上の言語エンティティを継承するモジュラー派生クラスです。クラス継承のセマンティクスは言語ごとに異なりますが、通常、サブクラスはそのスーパークラス のインスタンス変数メンバー関数を自動的に継承します。

派生クラスを定義する一般的な形式は次のとおりです。[9]

クラス サブクラス可視性スーパークラス  
{{
    //サブクラスメンバー
};
  • コロンは、サブクラスがスーパークラスから継承することを示します。可視性はオプションであり、存在する場合は、プライベートまたはパブリックのいずれかになります。デフォルトの可視性はprivateです。可視性は、基本クラスの機能がプライベート派生であるかパブリック派生であるかを指定します。

一部の言語は、他の構成の継承もサポートしています。たとえば、Eiffelでは、クラスの仕様を定義するコントラクトも相続人に継承されます。スーパークラスは、共通のインターフェイスと基本的な機能を確立します。これらの機能は、特殊なサブクラスが継承、変更、および補足できます。サブクラスによって継承されたソフトウェアは、サブクラスで再利用されていると見なされます。クラスのインスタンスへの参照は、実際にはそのサブクラスの1つを参照している場合があります。参照されているオブジェクトの実際のクラスをコンパイル時に予測することは不可能です統一されたインターフェイスは、さまざまなクラスのオブジェクトのメンバー関数を呼び出すために使用されます。サブクラスは、スーパークラス関数を、同じメソッドシグネチャを共有する必要があるまったく新しい関数に置き換えることができます

サブクラス化できないクラス

一部の言語では、クラス宣言に特定のクラス修飾子を追加することにより、クラスをサブクラス化不可として宣言できます。例としては、JavaおよびC ++ 11以降のキーワードやC#のキーワードがあります。このような修飾子は、キーワードおよびクラス識別子宣言の前にクラス宣言に追加されます。このようなサブクラス化できないクラスは、特に開発者がプリコンパイルされたバイナリにのみアクセスでき、ソースコードにはアクセスできない場合に、再利用性を制限します。 finalsealedclass

サブクラス化できないクラスにはサブクラスがないため、コンパイル時に、そのクラスのオブジェクトへの参照またはポインターが、サブクラスのインスタンス(存在しない)やスーパークラスのインスタンスではなく、実際にそのクラスのインスタンスを参照していると簡単に推測できます。 (参照型をアップキャストすると、型システムに違反します)。参照されるオブジェクトの正確なタイプは実行前にわかっているため、遅延バインディング動的ディスパッチとも呼ばれます)の代わりに早期バインディング静的ディスパッチとも呼ばれます)を使用できます。これには、多重継承かどうかに応じて1つ以上の仮想メソッドテーブルルックアップが必要です。またはのみ単一継承は、使用されているプログラミング言語でサポートされています。

オーバーライドできないメソッド

クラスがサブクラス化できない場合と同様に、メソッド宣言には、メソッドがオーバーライドされないようにする(つまり、サブクラス内で同じ名前と型の署名を持つ新しい関数に置き換えられる)メソッド修飾子が含まれる場合があります。プライベートメソッドは、それがメンバー関数であるクラス以外のクラスからアクセスできないという理由だけでオーバーライドできません(ただし、これはC ++には当てはまりません)finalJavaのメソッドsealed、C#のメソッド、またはfrozenEiffelの機能はオーバーライドできません。

仮想メソッド

スーパークラスメソッドが仮想メソッドの場合、スーパークラスメソッドの呼び出しは動的にディスパッチされます。一部の言語では、メソッドを仮想として明確に宣言する必要があり(C ++など)、他の言語では、すべてのメソッドが仮想(Javaなど)です。非仮想メソッドの呼び出しは常に静的にディスパッチされます(つまり、関数呼び出しのアドレスはコンパイル時に決定されます)。静的ディスパッチは動的ディスパッチよりも高速であり、インライン展開などの最適化が可能です。

継承されたメンバーの可視性

次の表は、クラスの派生時に指定された可視性に応じて、どの変数と関数が継承されるかを示しています。[10]

基本クラスの可視性 派生クラスの可視性
パブリック派生 プライベート派生 保護された派生
  • プライベート→
  • 保護→
  • 公開→
  • 継承されません
  • 保護されています
  • 公衆
  • 継承されません
  • プライベート
  • プライベート
  • 継承されません
  • 保護されています
  • 保護されています

アプリケーション

継承は、2つ以上のクラスを相互に関連付けるために使用されます。

オーバーライド

メソッドのオーバーライドの図

多くのオブジェクト指向プログラミング言語では、クラスまたはオブジェクトが、継承したアスペクト(通常は動作)の実装を置き換えることができます。このプロセスはオーバーライドと呼ばれますオーバーライドすると、複雑さが生じます。継承されたクラスのインスタンスが使用する動作のバージョンは、それ自体のクラスの一部であるのか、それとも親(基本)クラスのインスタンスであるのか。答えはプログラミング言語によって異なり、一部の言語では、特定の動作がオーバーライドされず、基本クラスで定義されたとおりに動作する必要があることを示す機能が提供されます。たとえば、C#では、基本メソッドまたはプロパティは、仮想、抽象、またはオーバーライド修飾子でマークされている場合にのみサブクラスでオーバーライドできますが、Javaなどのプログラミング言語では、他のメソッドをオーバーライドするためにさまざまなメソッドを呼び出すことができます。[11]オーバーライドの代わりに、継承されたコード を非表示にすることもできます。

コードの再利用

実装の継承は、サブクラスが基本クラスのコードを再利用するメカニズムです。デフォルトでは、サブクラスは基本クラスのすべての操作を保持しますが、サブクラスは一部またはすべての操作をオーバーライドして、基本クラスの実装を独自のものに置き換えることができます。

次のPythonの例では、サブクラスSquareSumComputerCubeSumComputerが基本クラスSumComputerのtransform()メソッドをオーバーライドします基本クラスは、2つの整数間の2の合計を計算する操作で構成されます。サブクラスは、数値を平方に変換する操作を除いて、基本クラスのすべての機能を再利用し、数値をそれぞれ平方立方体に変換する演算に置き換えます。したがって、サブクラスは2つの整数間の2乗/立方数の合計を計算します。

以下はPythonの例です。

クラス SumComputer 
    def  __init __ self  a  b ):
        self a  = 自己_ 
        b = b  

    def  transform self  x ):
        notImplementedErrorを 発生させます

    def 入力self 
        戻り 範囲self .a self .b _ 

    def  Compute self ):
        return  sum self .transform value for value in self .inputs     

クラス SquareSumComputer SumComputer ):
    def  transform self  x ):
        return  x  *  x

クラス CubeSumComputer SumComputer ):
    def  transform self  x ):
        return  x  *  x  *  x

ほとんどの場合、コードの再利用のみを目的としたクラスの継承は支持されなくなりました。[要出典]主な懸念事項は、実装の継承によってポリモーフィックな代替可能性が保証されないことです。再利用クラスのインスタンスを、継承されたクラスのインスタンスに置き換える必要はありません。別の手法である明示的な委任は、より多くのプログラミング作業を必要としますが、代替可能性の問題を回避します。[要出典] C ++では、プライベート継承を実装継承の形式として使用できます代替可能性なし。パブリック継承は「is-a」関係を表し、委任は「has-a」関係を表しますが、プライベート(および保護された)継承は「実装された」関係と考えることができます。[12]

継承のもう1つの頻繁な使用法は、クラスが特定の共通インターフェースを維持することを保証することです。つまり、同じメソッドを実装します。親クラスは、実装された操作と子クラスに実装される操作の組み合わせにすることができます。多くの場合、スーパータイプとサブタイプの間にインターフェイスの変更はありません。子は、親クラスの代わりに、説明されている動作を実装します。[13]

継承とサブタイピング

継承はサブタイピングと似ていますが、異なります。[3] サブタイピングにより、特定のタイプを別のタイプまたは抽象化に置き換えることができ、言語サポートに応じて、暗黙的または明示的に、サブタイプと既存の抽象化との間にis-a関係を確立すると言われています。この関係は、サブタイピングメカニズムとして継承をサポートする言語の継承を介して明示的に表現できます。たとえば、次のC ++コードは、クラスBAの間に明示的な継承関係を確立します。ここで、BはAのサブクラスとサブタイプの両方であり、 Bがどこにある場合でもAとして使用できます。(参照、ポインタ、またはオブジェクト自体を介して)指定されます。

クラス A { 
 パブリック
  void DoSomethingALike ()const {}   
};

クラス B パブリックA {    
 パブリック
  void DoSomethingBLike ()const {}   
};

void UseAnA const A a {    
  _ DoSomethingALike ();
}

void SomeFunc (){  
  B b ; 
  UseAnA b ); // bはAの代わりに使用できます。}  

サブタイピングメカニズムとして継承をサポートしないプログラミング言語では、基本クラスと派生クラスの関係は、間の関係と比較して、実装間の関係(コードの再利用のメカニズム)のみです。継承は、サブタイピングメカニズムとして継承をサポートするプログラミング言語であっても、必ずしも振る舞いサブタイピングを伴うとは限りません。親クラスが期待されるコンテキストで使用すると、オブジェクトが正しく動作しないクラスを派生させることは完全に可能です。リスコフの置換原則を参照してください[14] (外延/外延を比較。)一部のOOP言語では、サブタイプを宣言する唯一の方法は別のクラスの実装を継承する新しいクラスを定義することであるため、コードの再利用とサブタイピングの概念は一致します。

設計上の制約

プログラムの設計で継承を広範に使用すると、特定の制約が課せられます。

たとえば、人の名前、生年月日、住所、電話番号を含むクラスPersonについて考えてみます。個人の成績平均点と取得したクラスを含むStudentというサブクラスと、その個人の役職、雇用主、および給与を含むEmployeeという のサブクラスを定義できます。

この継承階層を定義する際に、すでに特定の制限を定義しましたが、すべてが望ましいわけではありません。

単一性
単一の継承を使用すると、サブクラスは1つのスーパークラスからのみ継承できます。上記の例を続けると、PersonはStudentまたはEmployeeのいずれかになりますが、両方にはなりません。多重継承を使用すると、 StudentEmployeeの両方から継承するStudentEmployeeクラスを定義できるため、この問題は部分的に解決されますただし、ほとんどの実装では、各スーパークラスから1回しか継承できないため、学生が2つの仕事をしている場合や2つの教育機関に通っている場合はサポートされません。Eiffelで利用可能な継承モデルは、繰り返し継承のサポートを通じてこれを可能にします。
静的
オブジェクトの継承階層は、オブジェクトのタイプが選択されたときのインスタンス化で固定され、時間とともに変化しません。たとえば、継承グラフでは、 Personスーパークラスの状態を保持しながらStudentオブジェクトをEmployeeオブジェクトにすることはできません。(ただし、この種の動作はデコレータパターンで実現できます。)継承を批判し、開発者を元の設計標準に固定すると主張する人もいます。[15]
可視性
クライアントコードがオブジェクトにアクセスできるときはいつでも、通常、すべてのオブジェクトのスーパークラスデータにアクセスできます。スーパークラスがパブリックとして宣言されていない場合でも、クライアントはオブジェクトをそのスーパークラスタイプにキャストできます。たとえば、学生のPersonスーパークラスに保存されているすべての個人データへのアクセスを関数に与えずに、関数に学生の成績平均点と成績証明書へのポインタを与える方法はありませんC ++やJavaを含む多くの現代言語は、継承チェーン外のコードがデータにアクセスすることを許可せずに、サブクラスがデータにアクセスできるようにする「保護された」アクセス修飾子を提供します。

複合再利用の原則は、継承の代替手段です。この手法は、動作をプライマリクラス階層から分離し、ビジネスドメインクラスで必要とされる特定の動作クラスを含めることにより、ポリモーフィズムとコードの再利用をサポートします。このアプローチは、実行時に動作を変更できるようにすることでクラス階層の静的な性質を回避し、1つのクラスがその祖先クラスの動作に制限されるのではなく、ビュッフェスタイルの動作を実装できるようにします。

問題と代替案

実装の継承は、少なくとも1990年代以降、オブジェクト指向プログラミングのプログラマーや理論家の間で物議を醸しています。その中には、代わりにインターフェイスの継承を提唱し、継承よりも構成を好むデザインパターンの作成者がいます。たとえば、クラス間の継承の静的な性質を克服するために、デコレータパターン(上記のとおり)が提案されています。同じ問題に対するより基本的な解決策として、役割指向プログラミングは、継承と構成のプロパティを新しい概念に組み合わせ、実行される明確な関係を導入します。[要出典]

Allen Holubによると、実装の継承に関する主な問題は、 「脆弱な基本クラスの問題」という形で不要な結合を導入することです。[5]基本クラスの実装を変更すると、サブクラスの動作が不注意に変化する可能性があります。実装は共有されず、APIのみが共有されるため、インターフェースを使用するとこの問題を回避できます。[15]これを述べる別の方法は、「継承はカプセル化を破る」ということです。[16]フレームワークなどのオープンオブジェクト指向システムでは、問題が明確に表面化しています。、クライアントコードはシステム提供のクラスから継承し、アルゴリズムでシステムのクラスの代わりに使用することが期待されます。[5]

伝えられるところによると、Javaの発明者であるJames Goslingは、実装の継承に反対し、Javaを再設計する場合は実装の継承を含めないと述べています。[15]継承をサブタイピング(インターフェース継承)から切り離す言語設計は、早くも1990年に登場しました。[17]これの現代的な例は、Goプログラミング言語です。

複雑な継承、または十分に成熟していない設計内で使用される継承は、ヨーヨー問題につながる可能性があります。1990年代後半に、システム内のコードを構造化するための主要なアプローチとして継承が使用されたとき、開発者は、システム機能が成長するにつれて、コードを継承の複数のレイヤーに自然に分割し始めました。開発チームが複数の継承レイヤーを単一責任の原則と組み合わせた場合、コードの非常に薄いレイヤーが多数作成され、その多くは各レイヤーに1行または2行のコードしかありません。レイヤーが多すぎると、どのレイヤーをデバッグする必要があるかを判断するのが難しくなるため、デバッグが重要な課題になります。

継承に関するもう1つの問題は、サブクラスをコードで定義する必要があることです。つまり、プログラムユーザーは実行時に新しいサブクラスを追加できません。他のデザインパターン(エンティティ-コンポーネント-システムなど)を使用すると、プログラムユーザーは実行時にエンティティのバリエーションを定義できます。

も参照してください

メモ

  1. ^ これは通常、 C ++ C#、Java、 Scalaなどの静的に型付けされたクラスベースのオブジェクト指向言語にのみ当てはまります

参考文献

  1. ^ ジョンソン、ラルフ(1991年8月26日)。「再利用可能なクラスの設計」 (PDF)www.cse.msu.edu
  2. ^ ミンツ、マイク; Ekendahl、Robert(2006)。C ++によるハードウェア検証:実践者向けハンドブックスプリンガー。p。22. ISBN 978-0-387-25543-9
  3. ^ a b クック、ウィリアムR。; ヒル、ウォルター; キャニング、ピーターS.(1990)。継承はサブタイピングではありませんプログラミング言語の原則に関する第17回ACMSIGPLAN-SIGACTシンポジウム(POPL)の議事録。pp。125–135。CiteSeerX10.1.1.102.8635_ 土井10.1145 /96709.96721ISBN  0-89791-343-4
  4. ^ Cardelli、Luca(1993)。タイプフルプログラミング(テクニカルレポート)。Digital EquipmentCorporationp。32〜33。SRCリサーチレポート45。
  5. ^ a b c しし座流星群、しし座流星群; Sekerinski、Emil(1998)。壊れやすい基本クラスの問題の研究(PDF)オブジェクト指向プログラミングに関する第12回欧州会議(ECOOP)の議事録。コンピュータサイエンスの講義ノート。1445.スプリンガー。pp。355–382。土井10.1007 / BFb0054099ISBN  978-3-540-64737-9
  6. ^ テンペロ、エワン; ヤン、ホンユル; ノーブル、ジェームズ(2013)。プログラマーがJavaの継承で行うこと(PDF)ECOOP 2013–オブジェクト指向プログラミング。コンピュータサイエンスの講義ノート。7920.スプリンガー。pp。577–601。土井10.1007 / 978-3-642-39038-8_24ISBN  978-3-642-39038-8
  7. ^ 「C ++継承」www.cs.nmsu.edu
  8. ^ Stroustrup、Bjarne(1994)。C ++の設計と進化ピアソン。p。417. ISBN 9780135229477
  9. ^ シルト、ハーバート(2003)。完全なリファレンスC ++タタ・マグロウヒル。p。 417ISBN 978-0-07-053246-5
  10. ^ Balagurusamy、E。(2010)。C ++を使用したオブジェクト指向プログラミングタタ・マグロウヒル。p。213. ISBN 978-0-07-066907-9
  11. ^ オーバーライド(C#リファレンス)
  12. ^ 「GotW#60:例外安全なクラス設計、パート2:継承」Gotw.ca。_ 2012年8月15日取得
  13. ^ ベヌゴパル、KR; Buyya、Rajkumar(2013)。C ++をマスターするタタマッグロウヒルエデュケーションプライベートリミテッド。p。609. ISBN 9781259029943
  14. ^ ミッチェル、ジョン(2002)。「10「オブジェクト指向言語の概念」 "。プログラミング言語の概念。ケンブリッジ大学出版 。p。287。ISBN 978-0-521-78098-8
  15. ^ a b c Holub、Allen(2003年8月1日)。「なぜ拡張するのは悪です」2015年3月10日取得
  16. ^ Seiter、Linda M。; パルスバーグ、イェンス; Lieberherr、Karl J.(1996)。「コンテキスト関係を使用したオブジェクトの動作の進化」ACMSIGSOFTソフトウェアエンジニアリングノート21(6) 46。CiteSeerX10.1.1.36.5053土井10.1145 /250707.239108 
  17. ^ アメリカ、ピエール(1991)。振る舞いサブタイピングを使用したオブジェクト指向プログラミング言語の設計オブジェクト指向言語の基礎に関するREXスクール/ワークショップ。コンピュータサイエンスの講義ノート。489. pp。60–90。土井10.1007 / BFb0019440ISBN 978-3-540-53931-5

さらに読む