継承 (プログラミング)

継承(inheritance)とは



コンピュータプログラミングにおける継承とは、既存のオブジェクトの特性を基にして、新しいオブジェクトを生成するためのメカニズムです。継承元のオブジェクトはまたはスーパークラス、継承先のオブジェクトはまたはサブクラスと呼ばれます。子オブジェクトは親オブジェクトの状態、機能、定数、アノテーションなどを引き継ぎますが、コンストラクタとデストラクタは引き継ぎの対象外となります。

クラスベースのオブジェクト指向プログラミング(OOP)では、親と子の関係はスーパークラスとサブクラスの関係で表現され、プロトタイプベースOOPでは、プロトタイプとクローンの関係で導入されます。

継承の概要



継承は、オブジェクトが持つ特性(データ、手続き、関数、定数、アノテーションなど)を引き継ぐ概念です。継承されたオブジェクトがどのような性質を持つか、どのように振る舞うかは任意です。引き継ぎの方法としては、オブジェクトが要求された特性を持たない場合、自動的に上位オブジェクトを検索する暗黙の委譲(delegation)ベースが一般的です。また、インスタンス化時に継承チェーンをたどり、重複要素を解決して1つの実体を生成する連結(concatenation)ベースも存在します。

継承は、既存のオブジェクトに新しい特性を追加して機能を拡張したり、共通の特性を上位ノードにまとめることでオブジェクトの分類体系化を促進します。これは差分プログラミングとも呼ばれ、プログラムの再利用性と保守性を高める効果があります。

継承とサブタイピングの違い



継承はサブタイピングと混同されがちです。サブタイピングとは、子オブジェクトが親オブジェクトに対して安全に代替可能であることを保証する継承です。一方、単なる継承は、親オブジェクトの特性を引き継ぐだけで、安全な代替可能性には関心がありません。

例えるなら、親の白黒映画をカラー映画化するのが代替可能なサブタイピングであり、親の白黒映画をグッズ販売に繋げるのが代替不可能な継承です。サブタイピングを実現するためには、リスコフの置換原則に従う必要があります。

継承とコンポジション



継承と対比される概念にコンポジション(合成)があります。サブタイピングとしての継承は、上位概念と下位概念の関係(Is-a)を表しますが、合成は「持つ」という関係(Has-a)を表します。継承がサブタイピングの目的で使用されない場合、スーパークラスとサブクラスの関係はIs-aでもHas-aでもないことがあります。そのため、状況に応じて継承と合成を使い分けることが重要になります。

継承の目的



継承の主な目的は、以下の2つです。

差分プログラミング



差分プログラミングは、クラス間の共通構成を、各クラスの特有構成に引き継がせることで、コードの重複を削減し、クラスの分類体系を構築する継承の用法です。これにより、クラスの機能拡張や、共通部分の体系化が可能になります。差分プログラミングは継承の本来の用法ですが、近年では階層分散されたデータやメソッドの把握が難しく、その弊害が目立つようになり、合成が代替として重視されるようになっています。

サブタイピング



サブタイピングは、スーパークラスのインスタンスを、サブクラスのインスタンスで安全に代替できることを指針にした継承の用法です。基底クラスの変数に派生クラスのインスタンスを代入しても安全であることが保証されます。これはIs-a関係とも言われます。

サブタイピングでは、派生クラスでのフィールドの追加は抑制され、基底クラスからのメソッド実装の引き継ぎも抑制されます。代わりに、基底クラスからのメソッド定義(メソッドシグネチャ)の引き継ぎが重視されます。派生インスタンスが代入された基底変数のメソッド名から、派生メソッドの内容が呼び出される言語機能は、メソッドオーバーライドと呼ばれ、その概念は動的ディスパッチと呼ばれます。サブタイピングは、動的ディスパッチに焦点を当てた継承と言えるでしょう。

実装継承は、具象メソッド(定義と実装)を引き継ぐことであり、界面継承は抽象メソッド(定義のみ)を引き継ぐことです。

Is-a関係のサブタイピングは、UMLクラス図では汎化/特化の関係として表現され、純粋抽象クラスであるインターフェースとの継承関係は、実現/実装の関係として表現されます。

多重継承



多重継承とは、クラスに複数のスーパークラスを持たせることです。単一継承とは異なり、多重継承では、どのメンバーが参照されるかの把握が困難になるという欠点があります。特に、フィールドの多重継承は原則禁止されています。メソッドの多重継承は、メソッド決定順序(MRO)問題を引き起こすことがあり、その解決策として、インターフェースの実装やトレイトのインクルードが導入されました。

また、多重継承におけるスーパークラスの重複による菱形継承問題も問題視されています。菱形継承問題の解決策としては、仮想継承、リネーミング、C3線形化などが存在します。

実装の多重継承は、そのデメリットから、後発の言語では制限される傾向にあり、代わりにインターフェースの多重継承が用いられるようになっています。

多重継承には、以下のような問題点があります。

継承関係が複雑になり、全体把握が困難になる。
名前の衝突が発生し、オーバーライドが難しくなる。
処理系の実装が複雑になる。
仮想継承を使用しない場合、同じ基底クラスが複数存在してしまう。

しかし、多重継承が直感的になる場合もあるため、どちらが正しいとは一概に言えません。

カプセル化と継承の可視性



カプセル化の可視性(public、protected、package、private)によって、スーパークラスのメンバーの継承が制限されます。privateメンバーはサブクラスに継承されず、packageメンバーは外部パッケージのサブクラスに継承されません。

継承の可視性は、スーパークラスメンバーの可視性にさらに制約を加える機能です。C++では、以下の3段階の継承が可能です。

public継承: そのままの継承。
protected継承: スーパークラスのpublicメンバーをprotectedメンバーに引き下げて継承。
* private継承: スーパークラスのpublic/protectedメンバーをprivateメンバーに引き下げて継承。

しかし、この機能は後継のOOP言語ではほとんど採用されていません。

ミックスイン



ミックスインは、多重継承問題の解決策として生まれた継承の方法論です。メソッドの集合体を継承することで、クラスに機能を追加することを目的としています。ミックスインは、トレイト、モジュール、プロトコル、ロールといった形態を取ります。トレイトの継承はインクルードと呼ばれることが多く、多重継承を前提としています。

界面継承のインターフェースとミックスイン継承のトレイトは、ともに多重継承を前提としているため、よく比較されます。インターフェースは抽象メソッドをクラスに継承させるのに対し、トレイトは独立メソッドをクラスに与えます。インターフェースは同名アルゴリズムをクラスに分散記述しますが、トレイトは1つのメソッドにアルゴリズムを一括記述します。インターフェースはデータメンバーを持たないことを想定していますが、トレイトはデータメンバーを持つことが可能です。

UMLにおける継承



UMLクラス図では、サブクラスから見たスーパークラスは汎化、スーパークラスから見たサブクラスは特化と呼ばれます。純粋抽象クラスはインターフェースと定義され、クラスから見たインターフェースは実現、クラスがインターフェースを継承することは実装と呼ばれます。

サブタイピングの継承は汎化/特化の関係で表現され、インターフェースの継承は実現/実装の関係で表現されます。ミックスインや差分プログラミングの継承は、UMLクラス図では扱われません。

まとめ



継承は、オブジェクト指向プログラミングにおける重要な概念であり、コードの再利用性や保守性を向上させるための強力なツールです。差分プログラミング、サブタイピング、多重継承、ミックスインなど、様々な側面があり、それぞれにメリットとデメリットが存在します。継承を正しく理解し、適切に使い分けることで、より効率的で質の高いプログラム開発が可能になります。

もう一度検索

【記事の利用について】

タイトルと記事文章は、記事のあるページにリンクを張っていただければ、無料で利用できます。
※画像は、利用できませんのでご注意ください。

【リンクついて】

リンクフリーです。