目次(ここを押すと開閉します)
継承
継承とは、あるクラスの特性を引き継いで派生させた、新たなクラスを作成すること。
ここで言うクラスの特性とは、属性やメソッドなどのことです。この特性を「引き継いで派生する」ことを継承すると言います。
継承では、継承元となったクラスをA、継承したクラスをBとした時、「BはAの一種である(B is a A)」という関係が成り立つ必要があります。この関係をis-a関係と言います。これは後述の「継承の目的(メリット)」で説明する、ポリモーフィズムで重要になってきます。
また、継承元クラス(A)を「親クラス」、継承したクラス(B)を「子クラス」と言う呼び方をします。呼び方は他にも様々あり、親クラスは「スーパークラス」、「ベースクラス(基底クラス)」等とも呼ばれます。子クラスは「サブクラス」、「派生クラス」等とも呼ばれます。
「is-a関係」を満たすには、子クラスは親クラスを特化したものである必要があります。逆に、親クラスは子クラスを汎化したものになります。特化/汎化は言い換えると具体化/抽象化とも言えます。
※汎化とはあまり聞き慣れない言葉ですが、継承関係を表す場合に使われます。「汎用的にする、一般化する」といった意味です。
「自動車」クラスで継承関係を考えてみる
自動車をより具体化すると、ガソリン自動車、電気自動車、ハイブリッド自動車等が考えられます。確かに「ガソリン自動車は自動車の一種」であり、「is-a関係」が成り立っています。他も同様です。
もう少し詳しく言うと、ガソリン自動車は自動車としての特性(アクセルを踏んだら走る等)を持ち、動力をガソリンエンジンに限定=特化したものです。
この例では自動車を親クラス、ガソリン自動車などを子クラスとした継承関係にあると言えます。また、自動車を汎化すると、「乗り物」などが考えられますし、ガソリン自動車を特化すると具体的な車種名などが考えられます。
「乗り物」-「自動車」-「ガソリン自動車」-「○○」(車種名)の様に、親子だけでなく、孫、ひ孫・・・と継承することができます。ただし、直接の親子だけでなく、親と孫、親とひ孫など、全ての組み合わせで「is-a関係」を満たしている必要があります。満たしていない場合は、継承するべきではありません。
継承の目的(メリット)
先ほどの自動車の例で説明します。
運転手は免許を持っていれば自動車を運転することができます。ガソリン自動車でも、電気自動車でも同じ様に扱えます。また、どの車種であっても問題ありません。
これを言い換えると、運転手は親クラスである「自動車」の扱い方さえわかっていれば、その子クラスであるどの自動車でも扱えるということです。自分が乗ったことのない車種をレンタルしても、すぐに運転できますよね。
どんな車種でも、アクセルとブレーキ、ハンドルを使って操作するということに変わりはありません。なので、特定の車種を意識する必要はないわけです。
この様に、運転手=「ユーザー」は、実際に乗っている車種=「子クラスのインスタンス」を意識せず、自動車=「親クラス」として扱えることを、ポリモーフィズム(多態性)と言います。
このポリモーフィズムは、前述した「is-a関係」を満たしていなければ実現しません。自動車の中に飛行機が混ざっていたら、運転手は扱えませんから。
継承の目的(メリット)は、ポリモーフィズム(多態性)が実現できること。
子クラスのインスタンスを意識せずに、親クラスと同じ様に扱えることがポリモーフィズムであり、継承のメリットです。ただ、ポリモーフィズムがなぜメリットになるか理解するには、もう1つ、継承の持つ特徴を知る必要があります。その特徴とはオーバーライドと言われるものです。
メソッドのオーバーライド
先ほどの例を、「アクセルを踏む」という操作に注目してさらに考えてみます。
どの車種でも「加速する」という目的は共通ですが、加速の度合いや、最高速度などの結果には違いがあります。運転手(ユーザー)が自動車(親クラス)の持つ「アクセルを踏む」という操作(メソッド)を実行したとき、実際に乗っている車種(子クラスのインスタンス)によって処理内容と結果が変わるということです。
これは、「アクセルを踏む」メソッドの実装(仕様)が子クラスによって違うからです。言い換えると、親クラスの「アクセルを踏む」メソッドの実装を、子クラスで上書きしているのです。
この様に、親クラスのメソッドを上書きして実装することをオーバーライドといいます。(「上書き」をそのまま英訳したのと同じです。上書きするイメージと関連付けて覚えましょう。)
インスタンスによってメソッドの処理内容・結果が変わる=変えられる
インスタンス化した子クラスで親クラスのメソッドがオーバーライドされていると、他のクラスと同じ属性や引数を与えたとしても実行結果は異なります。オーバーライドでは、その子クラスが自分自身に特化した処理を行うことができるからです。
ユーザーは加速するという目的を果たせれば、処理内容が異なっても気になりません。この様に、ユーザーは各子クラスの詳細を知らなくても、インスタンス化したクラスに適した処理が行われます。
言い換えれば、ユーザーは親クラスの使い方さえわかっていれば、インスタンスを変えるだけで、それに適した結果が得られ、目的を果たすことができるのです。子クラスの処理の詳細まで知る必要はありません。
まとめ
継承とは、親クラスの特性を引き継いで派生させた、子クラスを定義すること。
継承の目的(メリット)は、ポリモーフィズム(多態性)が実現できること。
メソッドのオーバーライドとポリモーフィズムにより、ユーザーは親クラスの扱いかたさえ知っていれば良い。子クラスの詳細を知らなくても、インスタンス化したクラスに応じた結果が得られる。
継承、ポリモーフィズムは、カプセル化と合わせて、オブジェクト指向の3大要素と言われる重要な概念です。特にポリモーフィズムは理解が難しいと思いますが、非常に重要です。後日公開する【実践編】で理解を深めましょう。
補足:処理の共通化(コードの再利用)が継承の目的・メリット?
継承のメリットとして、処理の共通化もよく挙げられます。
処理の共通化とは、子クラスで必要な処理(メソッド)を親クラスに実装し、子クラスで同じ処理を改めて実装する無駄を省くことです。プログラムのコードを親クラスに記述して、子クラスはそれを利用するので、コードの再利用の様にも言われます。
これは確かにメリットではあります。同じ処理をするコードが複数あると、保守(バグの修正など)も大変になるので、同じコードは1ヶ所にするべきです。(DRY原則と呼ばれます。)
ただし、処理の共通化を目的に継承を考えることはやめた方がいいでしょう。「is-a関係」を満たさない継承になりやすく、保守性や拡張性などに悪影響が出ます。
クラスを考える場合、ユーザーが「自動車」を扱いたいのであれば、まず「自動車」クラスを考えます。そして自動車の実装を考え、自動車の種類によって実装内容を変える必要があるのなら、継承して子クラスを作りましょう。
この時、「自動車として共通する処理」=「全ての子クラスで必要な処理」だけを自動車クラスに実装しましょう。
また、実装の詳細は子クラスによって異なるものの、全ての子クラスが持つべき処理というのもあります。例で挙げた「アクセルを踏む」等です。これも親クラスにメソッドを持たせるのですが、処理の実装はできません。
この様な場合は、オーバーライドされることを前提として実装がないメソッドを作ります。これについては次頁の「抽象:抽象クラスとインターフェース」で解説します。
補足:継承と委譲について
継承の話をするとよく一緒に付いてくるのが委譲というものです。
委譲とは他のクラスに処理を「委譲=まかせる」ことです。
「is-a関係」にないクラスAとBがあり、この2つに共通の処理があったとします。
同じ処理をAとBの両方に書くことは無駄であり、保守性にも悪影響が出ます。(DRY原則)
しかし、処理の共通化を目的に、「is-a関係」にないものを継承すべきではありません。
この様な場合は、共通処理部分を担う別のクラスCに委譲するのです。要するに、AおよびBはそれぞれ、Cのメソッドを呼び出して共通処理部分をまかせます。これで、継承を無理に使うことなく処理の共通化ができます。
補足:継承元と継承先クラスの呼び方について
ここまでは継承元のクラスを親クラス、継承先を子クラスと呼んできました。これは、親子、孫、ひ孫・・・という3代以上の継承を少しでもイメージしてもらうためでした。
一般的にはスーパークラスとサブクラスが多く使われていると思います。なので、これ以降の記事では基本的に「スーパークラス」、「サブクラス」と呼ぶこととします。
ただ、他の呼び名も間違いではなく、話す人や状況によって変わるので、どれも同じものであることは認識しておいてください。
この記事へのコメントはありません。