オブジェクト指向プログラミングにおいて、継承とは、オブジェクトまたはクラスを別のオブジェクト(プロトタイプベース継承)またはクラス(クラスベース継承)に基づいて構築し、同様の実装を維持するメカニズムです。また、スーパークラスや基底クラスなどの既存のクラスから新しいクラス(サブクラス)を派生させ、それらをクラス階層に構成することも継承と定義されます。C ++などのほとんどのクラスベースオブジェクト指向言語では、継承によって作成されたオブジェクト(「子オブジェクト」)は、「親オブジェクト」のすべてのプロパティと動作(基底クラスのコンストラクタ、デストラクタ、オーバーロード演算子、フレンド関数を除く)を引き継ぎます。継承により、プログラマは既存のクラスに基づいてクラスを作成したり、[ 1 ]同じ動作を維持しながら新しい実装を指定したり(インターフェースを実現)、コードを再利用したり、パブリッククラスやインターフェースを介して元のソフトウェアを独自に拡張したりすることが可能になります。継承を介したオブジェクトまたはクラスの関係は、有向非巡回グラフを形成します。
継承されたクラスは、親クラスまたはスーパークラスのサブクラスと呼ばれます。 「継承」という用語は、クラスベースプログラミングとプロトタイプベースプログラミングの両方で広く用いられますが、狭義にはクラスベースプログラミング(あるクラスが別のクラスを継承する)を指し、プロトタイプベースプログラミングにおける対応する手法は「委任」(あるオブジェクトが別のオブジェクトに委任する)と呼ばれます。クラスを変更する継承パターンは、単純なネットワークインターフェースパラメータに基づいて事前に定義することができ、言語間の互換性が維持されます。[ 2 ] [ 3 ]
継承とサブタイピングを混同しないでください。[ 4 ] [ 5 ] C++、C#、 Java 、Scalaなど、一般的に静的に型付けされたクラスベースのオブジェクト指向言語では、継承とサブタイピングは一致する言語もありますが、他の言語では異なります。 一般的に、サブタイピングはis-a関係を確立しますが、継承は実装を再利用して構文関係を確立するだけで、必ずしも意味関係を確立するわけではありません(継承では動作のサブタイピングが保証されるわけではありません)。 これらの概念を区別するために、サブタイピングはインターフェース継承と呼ばれることもあります(型変数の特殊化によってもサブタイピング関係が誘導されることは認識しません)。 一方、ここで定義されている継承は実装継承またはコード継承と呼ばれます。[ 6 ]それでも、継承はサブタイプ関係を確立するための一般的なメカニズムです。[ 7 ]
継承は、オブジェクト合成とは対照的です。オブジェクト合成では、あるオブジェクトが別のオブジェクトを包含する(またはあるクラスのオブジェクトが別のクラスのオブジェクトを包含する)ことになります。継承よりも合成の方が重要です。サブタイプのis-a関係とは対照的に、合成はhas-a関係を実現します。
数学的に言えば、あらゆるクラス システムでの継承は、そのシステム内のクラスのセットに 厳密な半順序を誘導します。
1966 年に、Tony Hoare はレコード、特にレコード サブクラス、共通のプロパティを持ちながらもバリアント タグで区別され、バリアントに対してプライベートなフィールドを持つレコード型というアイデアについて発表しました。[ 8 ]この影響を受けて、1967 年にOle-Johan DahlとKristen Nygaard は、異なるクラスに属しながらも共通のプロパティを持つオブジェクトを指定できる設計を発表しました。共通のプロパティはスーパークラスに集められ、各スーパークラス自体がスーパークラスを持つ可能性があります。したがって、サブクラスの値は複合オブジェクトであり、さまざまなスーパークラスに属するプレフィックス部分と、サブクラスに属するメイン部分で構成されます。これらの部分はすべて連結されています。[ 9 ]複合オブジェクトの属性には、ドット表記法でアクセスできます。このアイデアは、最初にSimula 67 プログラミング言語で採用されました。[ 10 ]その後、このアイデアはSmalltalk、C++、Java、Python、その他多くの言語に広まりました。


パラダイムと特定の言語に基づいて、さまざまな継承の種類があります。[ 11 ]
多重継承は 、効率的に実装するのが非常に難しいと広く考えられていました。例えば、ブラッド・コックスはObjective Cの著書におけるC++の要約の中で、C++に多重継承を追加することは不可能だと実際に主張していました。そのため、多重継承はより困難な課題と思われていました。私は1982年には既に多重継承について検討し、1984年にはシンプルで効率的な実装手法を発見していたため、この挑戦に抵抗できませんでした。これは、流行が一連の出来事に影響を与えた唯一の事例ではないかと考えています。[ 12 ]

// C++ 言語実装class A { ... }; // 基本クラスclass B : public A { ... }; // B は A から派生class C : public B { ... }; // C は B から派生サブクラス、派生クラス、継承クラス、または子クラスは、1つ以上の他のクラス(スーパークラス、基底クラス、または親クラスと呼ばれる)から1つ以上の言語エンティティを継承するモジュール式の派生クラスです。クラス継承のセマンティクスは言語によって異なりますが、一般的にサブクラスはスーパークラスの インスタンス変数とメンバー関数を自動的に継承します
C++では、派生クラスを定義する一般的な形式は次の通りである: [ 13 ]
class SubClass : visibility SuperClass { // サブクラスのメンバー};コロンは、サブクラスがスーパークラスから継承することを示します。可視性修飾子はオプションであり、指定する場合はprivateまたはpublic のいずれかになります。デフォルトの可視性(修飾子がない場合)はprivateです。可視性は、基底クラスの機能がprivate に派生されているかpublic に派生されているかを指定します。
Java や C# などの他の言語では、継承の可視性修飾子がないことに注意してください。
// 可視性修飾子なし// C++ の public SuperClass と同等class SubClass extends SuperClass { // サブクラスのメンバー}一部の言語では、他の構成要素の継承もサポートされています。たとえば、Eiffelでは、クラスの仕様を定義する契約も継承クラスによって継承されます。スーパークラスは共通のインターフェースと基本機能を確立し、特殊化されたサブクラスはこれを継承、変更、補足できます。サブクラスに継承されたソフトウェアは、サブクラスで再利用されているとみなされます。クラスのインスタンスへの参照は、実際にはそのサブクラスの 1 つを参照している場合があります。参照されているオブジェクトの実際のクラスをコンパイル時に予測することは不可能です。統一インターフェースは、さまざまなクラスのオブジェクトのメンバー関数を呼び出すために使用されます。サブクラスは、スーパークラスの関数を、同じメソッド シグネチャを共有する必要のあるまったく新しい関数に置き換えることができます。
一部の言語では、クラス宣言に特定のクラス修飾子を追加することで、クラスをサブクラス化できないと宣言できます。例としては、 JavaおよびC++11以降のキーワード、またはC#のキーワードなどがあります。このような修飾子は、キーワードとクラス識別子宣言の前にクラス宣言に追加されます。このようなサブクラス化できないクラスは、特に開発者がソースコードではなくコンパイル済みバイナリにしかアクセスできない場合に、再 利用性を制限しますfinalsealedclass
サブクラス化不可能なクラスにはサブクラスが存在しないため、コンパイル時に、そのクラスのオブジェクトへの参照またはポインタが実際にはそのクラスのインスタンスを参照しており、サブクラスのインスタンス(それらは存在しない)やスーパークラスのインスタンス(参照型のアップキャストは型システムに違反する)を参照していないことが容易に推測できます。参照されるオブジェクトの正確な型は実行前にわかっているため、遅延バインディング(動的ディスパッチとも呼ばれる)の代わりに、早期バインディング(静的ディスパッチとも呼ばれる)を使用できます。遅延バインディングでは、使用しているプログラミング言語で多重継承がサポートされているか単一継承のみがサポートされているかに応じて、 1つ以上の仮想メソッドテーブル参照が必要です。
クラスがサブクラス化できない場合と同様に、メソッド宣言にはメソッドのオーバーライド(つまり、サブクラス内で同じ名前と型シグネチャを持つ新しい関数に置き換えられること)を防ぐメソッド修飾子を含めることができます。プライベートメソッドは、単にそのメソッドがメンバー関数となっているクラス以外のクラスからアクセスできないため、オーバーライド不可能です(ただし、C++ではそうではありません)。Javafinalのメソッド、sealedC#のメソッド、またはfrozenEiffelの機能はオーバーライドできません
スーパークラスのメソッドが仮想メソッドである場合、そのスーパークラスのメソッドの呼び出しは動的にディスパッチされます。言語によっては、メソッドを明示的に仮想として宣言する必要があります(例:C++)。また、すべてのメソッドが仮想である必要がある言語もあります(例:Java)。非仮想メソッドの呼び出しは常に静的にディスパッチされます(つまり、関数呼び出しのアドレスはコンパイル時に決定されます)。静的ディスパッチは動的ディスパッチよりも高速で、インライン展開などの最適化が可能 です
次の表は、C++で確立された用語を使用して、クラスの派生時に与えられた可視性に応じてどの変数と関数が継承されるかを示しています。[ 14 ]
| 基底クラスの可視性 | 派生クラスの可視性 | ||
|---|---|---|---|
| プライベート派生 | 保護された派生 | 公開された派生 | |
|
|
|
|
継承は、2つ以上のクラスを相互に関連付けるために使用されます

多くのオブジェクト指向プログラミング言語では、クラスまたはオブジェクトが継承した側面(通常は動作)の実装を置き換えることができます。このプロセスはオーバーライドと呼ばれます。オーバーライドによって、継承されたクラスのインスタンスがどの動作バージョンを使用するか(そのクラスの一部であるもの、または親(基本)クラスのもの)という複雑な問題が生じます。答えはプログラミング言語によって異なり、特定の動作はオーバーライドされず、基本クラスの定義に従って動作する必要があることを示す機能を提供する言語もあります。たとえば、C#では、基本メソッドまたはプロパティは、virtual、abstract、またはoverride修飾子でマークされている場合にのみサブクラスでオーバーライドできますが、Javaなどのプログラミング言語では、異なるメソッドを呼び出して他のメソッドをオーバーライドすることができます。[ 15 ]オーバーライドの代替手段は、継承されたコードを非表示にすることです。
実装継承とは、サブクラスが基底クラスのコードを再利用するメカニズムです。デフォルトでは、サブクラスは基底クラスのすべての操作を保持しますが、サブクラスは一部またはすべての操作をオーバーライドし、基底クラスの実装を独自のものに置き換えること ができます
次の例では、サブクラスSquareSumComputerとが基底クラスのメソッドCubeSumComputerをオーバーライドしています。基底クラスは、2つの整数の平方和を計算する演算で構成されています。サブクラスは、数値をその平方に変換する演算を除く基底クラスのすべての機能を再利用し、数値をそれぞれ平方と立方に変換する演算に置き換えています。したがって、サブクラスは2つの整数の平方/立方和を計算します。 transform()SumComputer
以下は Java の例です。
java.util.Listをインポートします。java.util.stream.Collectorsをインポートします。java.util.stream.IntStreamをインポートします。抽象クラスSumComputer { private int a ; private int b ;パブリックSumComputer ( int a 、int b ) { this . a = a ; this . b = b ; }// サブクラスによって実装される抽象メソッドpublic abstract int transform ( int x );パブリックList < Integer > inputs ( ) { return IntStream.rangeClosed ( a , b ) .boxed.collect ( Collectors.toList ( ) ) ; }public int compute () { return inputs (). stream () . mapToInt ( this :: transform ) . sum (); } }クラスSquareSumComputer はSumComputerを拡張します{ public SquareSumComputer ( int a , int b ) { super ( a , b ); }@Override public int transform ( int x ) { return x * x ; } }クラスCubeSumComputer はSumComputerを拡張します{ public CubeSumComputer ( int a , int b ) { super ( a , b ); }@Override public int transform ( int x ) { return x * x * x ; } }コードの再利用のみを目的としたクラス継承は、多くの方面で支持されなくなっています。主な懸念事項は、実装継承では多態的な代替可能性が保証されないことです。つまり、再利用クラスのインスタンスが、必ずしも継承先のクラスのインスタンスに置き換えられるとは限りません。代替手法である明示的な委譲は、プログラミングの手間はかかりますが、代替可能性の問題を回避できます。C++では、代替可能性のない実装継承の一種として、プライベート継承を使用できます。パブリック継承が「is-a」関係を表し、委譲が「has-a」関係を表すのに対し、プライベート(およびプロテクト)継承は「is-in-terms of」関係と考えることができます。[ 16 ]
継承のもう一つの頻繁な用途は、クラスが特定の共通インターフェースを維持することを保証することです。つまり、クラスは同じメソッドを実装します。親クラスは、実装された操作と子クラスで実装される操作の組み合わせになります。多くの場合、スーパータイプとサブタイプの間でインターフェースの変更はなく、子クラスが親クラスの代わりに記述された動作を実装します。[ 17 ]
継承はサブタイプ化に似ていますが、異なります。[ 4 ]サブタイプ化は、特定の型を別の型または抽象化に置き換えることを可能にし、言語サポートに応じて暗黙的または明示的に、サブタイプと既存の抽象化の間にis-a関係を確立すると言われています。この関係は、サブタイプ化メカニズムとして継承をサポートする言語では、継承を介して明示的に表現できます。たとえば、次のC++コードは、クラスBとAの間に明示的な継承関係を確立します。ここで、BはAのサブクラスとサブタイプの両方であり、 Bが指定されている場合はどこでも(参照、ポインタ、またはオブジェクト自体を介して)、 Aとして使用できます
クラスA { public : void methodOfA () const { // ... } };クラスB : public A { public : void methodOfB () const { // ... } };void functionOnA ( const A & a ) { a . methodOfA (); }int main () { B b ; functionOnA ( b ); // b は A の代わりに使用できます。 }継承をサブタイプ化メカニズムとしてサポートしないプログラミング言語では、基底クラスと派生クラスの関係は、型間の関係と比較すると、実装間の関係(コード再利用のメカニズム)にすぎません。継承をサブタイプ化メカニズムとしてサポートするプログラミング言語でも、継承は必ずしも動作のサブタイプ化を伴うわけではありません。親クラスが期待されるコンテキストで使用されるとオブジェクトが誤って動作するクラスを派生することは完全に可能です。Liskovの置換原則を参照してください。[ 18 ](connotation/denotationを参照)。一部の OOP 言語では、サブタイプを宣言する唯一の方法が、別のクラスの実装を継承する新しいクラスを定義することであるため、コードの再利用とサブタイプ化の概念が一致する場合があります。
プログラムの設計において継承を多用すると、特定の制約が課せられます
例えば、Person人物の名前、生年月日、住所、電話番号を含むクラスを考えてみましょう。Person のサブクラスとして、人物の成績平均点と履修科目を含むクラスを定義し、さらにPersonのサブクラスとして、人物の役職、勤務先、給与を含む Employeeクラスを定義することができます。Student
この継承階層を定義する際に、すでに特定の制限を定義しましたが、そのすべてが望ましいわけではありません。
Personいずれかであり、両方を継承することはできません。多重継承を使用すると、 と の両方を継承するクラスを定義できるため、この問題は部分的に解決されます。ただし、ほとんどの実装では、各スーパークラスから継承できるのは1回のみであるため、学生が2つの仕事を掛け持ちしたり、2つの教育機関に通ったりするケースをサポートしていません。Eiffelで利用可能な継承モデルは、反復継承をサポートすることでこれを可能にします。StudentEmployeeStudentEmployeeStudentEmployeeStudentオブジェクトがオブジェクトになることは許可されません。(ただし、この種の動作はデコレータパターンによって実現できます。)継承は開発者を元の設計基準に縛り付けるとして批判する人もいます。[ 19 ]EmployeePersonStudentその関数に学生のPersonスーパークラスに格納されているすべての個人データへのアクセスも許可しなければなりません。C++やJavaを含む多くの現代言語は、「protected」アクセス修飾子を提供しています。この修飾子により、継承チェーンの外側にあるコードはアクセスできなくなりますが、サブクラスはデータにアクセスできます。複合再利用原則は、継承に代わるものです。この手法は、動作を主要なクラス階層から分離し、必要に応じて特定の動作クラスをビジネスドメインクラスに含めることで、ポリモーフィズムとコードの再利用をサポートします。このアプローチは、実行時に動作を変更できるようにすることでクラス階層の静的な性質を回避し、あるクラスが祖先クラスの動作に制限されることなく、ビュッフェスタイルで動作を実装できるようにします。
実装継承は、少なくとも1990年代以降、オブジェクト指向プログラミングのプログラマーや理論家の間で議論の的となっています。批判者の中には、デザインパターンの著者がおり、彼らは代わりにインターフェース継承を主張し、継承よりもコンポジションを好みます。例えば、デコレータパターン(前述のように)は、クラス間の継承の静的な性質を克服するために提案されました。同じ問題に対するより根本的な解決策として、ロール指向プログラミングは、継承とコンポジションの特性を組み合わせた新しい概念である 、 played-byという明確な関係を導入します
アレン・ホルブによると、実装継承の主な問題は、「脆弱な基底クラス問題」という形で不要な結合が生じることである。[ 6 ]基底クラスの実装を変更すると、サブクラスの動作が意図せず変更される可能性がある。インターフェースを使用すれば、実装は共有されずAPIのみが共有されるため、この問題は回避される。[ 19 ]別の言い方をすれば、「継承はカプセル化を破壊する」ということである。[ 20 ]この問題は、フレームワークなどのオープンなオブジェクト指向システムで顕著に現れる。これらのシステムでは、クライアントコードはシステムが提供するクラスを継承し、アルゴリズムの中でシステムのクラスに置き換えられる。[ 6 ]
伝えられるところによると、Javaの発明者であるジェームズ・ゴスリングは実装継承に反対しており、Javaを再設計するなら実装継承は含めないと述べた。[ 19 ]継承をサブタイピング(インターフェース継承)から切り離す言語設計は、1990年代初頭に登場した。[ 21 ]その現代的な例としては、Goプログラミング言語が挙げられる。
複雑な継承、あるいは十分に成熟していない設計の中で継承が使用されると、ヨーヨー問題が発生する可能性があります。1990年代後半、継承がプログラム構造の主要なアプローチとして用いられていた頃、開発者はシステムの機能が拡張されるにつれて、コードをより多くの継承層に分割する傾向がありました。開発チームが複数の継承層と単一責任の原則を組み合わせた場合、結果として非常に薄いコード層が多数作成され、実際には1行か2行のコードしか含まれない層が多くなりました。層が多すぎると、どの層をデバッグする必要があるかを判断するのが難しくなり、デバッグが大きな課題となります。
継承のもう一つの問題は、サブクラスをコード内で定義する必要があることです。つまり、プログラムユーザーは実行時に新しいサブクラスを追加できません。他の設計パターン(エンティティ-コンポーネント-システムなど)では、プログラムユーザーは実行時にエンティティのバリエーションを定義できます。