抽象クラスとインターフェイスで設計するクラス階層

強いプログラムは、クラスを増やす前に、階層を整えるところから始まる。
抽象クラスとインターフェイスを使い分けると、ドラゴンボールの戦士たちのように、共通ルールと個性をきれいに整理できる。

12章で学んできた抽象クラスとインターフェイスは、どちらもオブジェクト指向の設計を支える大切なしくみです。
最初は文法の違いとして見えていたかもしれませんが、実はこの2つは、大きなプログラムを作るときにこそ本当の力を発揮します。

クラスが少ないうちは、1つ1つを個別に扱っても何とかなることがあります。
けれども、登場するクラスが増えてくると、

  • どのクラスに共通の性質があるのか
  • どのクラスが同じルールで扱えるのか
  • 新しいクラスを追加するときに、どこへ組み込めばよいのか

を整理しないと、コードはすぐに複雑になってしまいます。

ドラゴンボールでたとえると、悟空、ベジータ、ピッコロ、悟飯、トランクスのように、戦士が少ないうちは1人ずつ個別に考えても大きな問題はありません。
でも、戦士の数がどんどん増えてくると、

  • 戦士として共通に持つべき土台は何か
  • 気を使える者のルールは何か
  • 飛べる者のルールは何か
  • どのキャラクターを同じ型としてまとめて扱えるのか

をはっきりさせたくなります。

ここで役立つのが、抽象クラスとインターフェイスを組み合わせたクラス階層の設計です。

抽象クラスは、共通の土台をまとめるのに向いています。
インターフェイスは、何ができるかという約束を整理するのに向いています。
この2つをうまく組み合わせると、多くのクラスをまとめて扱いやすくなり、新しいクラスの追加や差し替えもしやすくなります。

つまり、クラス階層を設計するというのは、単に親子関係を作ることではありません。
共通部分をどこに置き、約束をどこで定義し、個性をどこに任せるかを整理すること です。

今回は、「抽象クラスとインターフェイスで設計するクラス階層」という視点で、12章の内容をドラゴンボールの世界観に置きかえながら、わかりやすく整理していきます。

クラス階層を設計するとはどういうことか

クラス階層を設計するとは、似た役割を持つクラスどうしの関係を整理して、どのクラスを親にし、どのクラスを子にし、どの約束をインターフェイスとして分けるかを考えることです。

オブジェクト指向では、クラスをただ並べるだけではなく、共通する考え方を上位にまとめ、違いを下位のクラスへ分けていきます。

ドラゴンボールで考えると、これは戦士たちを整理する作業に似ています。

たとえば、

  • すべての戦士に共通する性質
  • サイヤ人だけが持つ特徴
  • 気を扱える者が共通して持つ能力
  • 飛行できる者が共通して持つ能力

を整理していくと、キャラクターの関係がかなり見やすくなります。

プログラムでも同じです。
共通するものをまとめておくと、あとから新しいクラスを増やしても、全体が崩れにくくなります。

抽象クラスは共通の土台を作るためのもの

抽象クラスは、複数のクラスに共通する土台をまとめるために使います。

たとえばドラゴンボール風に Warrior という抽象クラスを考えるとします。
戦士なら共通して、

  • 名前を持つ
  • 速度を持つ
  • 基本的な情報表示の仕組みを持つ

といった性質があるかもしれません。

このような「みんなが共通して持つもの」は、抽象クラスにまとめるのが向いています。

ただし、すべてを親クラスで完成させる必要はありません。
たとえば show() のような表示処理は、戦士ごとに違っていてもよいはずです。

  • サイヤ人なら戦闘力を見せる
  • ピッコロなら特技や再生能力を見せる
  • 地球人戦士なら武器や戦闘スタイルを見せる

このように、共通の枠だけを親クラスに置き、中身は子クラスで完成させるのが抽象クラスの考え方です。

つまり抽象クラスは、
共通の状態と共通の考え方をまとめるための土台
だと考えるとわかりやすいです。

インターフェイスは能力や約束を整理するためのもの

インターフェイスは、「何ができるか」という共通ルールを整理するために使います。

ドラゴンボールで考えると、キャラクターたちには種族や立場とは別に、いろいろな能力があります。

  • 飛べる
  • 気を使える
  • 情報を表示できる
  • 特定の技を使える

こうした能力は、「サイヤ人だから持つ」とは限りません。
ピッコロも飛べますし、悟空も飛べますし、ほかの戦士も気を使えるかもしれません。

つまりインターフェイスは、キャラクターの種類ではなく、共通の能力の約束 を表すのに向いています。

たとえば、

  • iMovable は 動ける者の約束
  • iKiUser は 気を使える者の約束
  • iDisplayable は 情報を表示できる者の約束

というように整理できます。

こうしておくと、見た目の違うクラスでも、同じインターフェイスを実装していれば、同じルールでまとめて扱えるようになります。

抽象クラスとインターフェイスを組み合わせると設計しやすい

実際の設計では、抽象クラスとインターフェイスのどちらか一方だけで考えるより、両方を組み合わせたほうが自然なことが多いです。

ドラゴンボール風に整理すると、次のような役割分担が考えられます。

役割向いているしくみ
戦士としての共通の土台抽象クラス
飛べるという約束インターフェイス
気を使えるという約束インターフェイス
実際のキャラクター具体クラス

たとえば、

  • Warrior 抽象クラスで共通の名前や速度を持たせる
  • iMovable で移動できることを表す
  • iKiUser で気を使えることを表す
  • Saiyan や NamekianWarrior で具体的なキャラクターを表す

という構成にすると、とても整理しやすくなります。

この形だと、

  • 共通の土台は抽象クラス
  • 共通の能力はインターフェイス
  • 個性は具体クラス

と、役割がはっきり分かれます。

多くのクラスをまとめて扱えることが大きな利点

抽象クラスやインターフェイスを使うと、多くのクラスをまとめて扱えるようになります。

これは大規模なプログラムではとても大きな利点です。

ドラゴンボールで考えると、たとえば iWarrior のようなインターフェイスを設計しておけば、

  • Saiyan
  • NamekianWarrior
  • EarthWarrior

のような異なるクラスでも、同じ iWarrior 型として扱えます。

そうすると、使う側のコードは個別のクラス名を意識しなくても、

  • show()
  • move()

のような共通メソッドだけを使って整理できます。

これによって、

  • 同じ処理を書きやすくなる
  • 新しいクラスを追加しやすくなる
  • 差し替えしやすくなる
  • 全体の見通しがよくなる

という効果が生まれます。

新しいクラスの追加や差し替えがしやすくなる

クラス階層をきちんと設計しておくと、新しいクラスを追加したり、既存のクラスを差し替えたりすることがとても楽になります。

たとえばドラゴンボール風に、

  • Saiyan
  • NamekianWarrior

をすでに使っているプログラムがあるとします。

ここに新しく

  • AndroidWarrior
  • FusionWarrior

のようなクラスを追加したくなったとき、それらが同じ抽象クラスやインターフェイスのルールに従っていれば、既存のコードに自然に組み込めます。

つまり、使う側のコードは
「このクラス名は何か」
ではなく、
「この共通ルールを守っているか」
を見ればよくなります。

これが、クラス階層を設計しておくことの大きな意味です。

インターフェイスは多重継承の考え方も支えられる

本文でも説明されているように、インターフェイスは多重継承の機能を持たせることができます。

Javaではクラスの多重継承はできません。
でもインターフェイスなら、複数の約束を同時に持たせることができます。

ドラゴンボールで考えると、1人の戦士が

  • 戦士としての共通土台を持ちながら
  • 飛べる
  • 気を使える
  • 情報を表示できる

といった複数の能力ルールを引き受けることができます。

つまり、

  • クラスの継承で土台を整理する
  • インターフェイスで能力を柔軟に追加する

という役割分担ができるわけです。

この考え方があると、クラスだけでは表現しにくい複雑な関係も、かなり自然に整理できます。

クラス階層を設計するということは、役割分担を決めること

クラス階層を設計するとは、単に親子関係を作ることではありません。
本当に大事なのは、どの役割をどこへ置くか を決めることです。

整理すると、こうなります。

置き場所置く内容
抽象クラス共通の状態、共通の処理、共通の土台
インターフェイス共通の能力、共通の約束、必ず持つメソッド名
具体クラス実際の処理内容、個別の特徴、キャラクターらしさ

この分担ができると、コードがかなり読みやすくなります。

ドラゴンボールで言えば、

  • 抽象クラスは「戦士としての基本」
  • インターフェイスは「飛べる」「気を使える」という能力ルール
  • 具体クラスは「ベジータ」「ピッコロ」のような実際のキャラクター

です。

この役割分担こそが、クラス階層を設計するということです。

標準クラスライブラリも階層を持っている

本文では、標準クラスライブラリのクラスも階層を持っていることが説明されています。
実際にクラスを使うときは、

  • どんなクラスから拡張されているのか
  • どんなインターフェイスを実装しているのか

を調べると、そのクラスの役割がかなりわかりやすくなります。

たとえば Math クラスが Object クラスを拡張している、という関係を見るだけでも、そのクラスが Java 全体のクラス階層の中でどこに位置しているかが見えてきます。

ドラゴンボール風に言えば、新しい戦士が出てきたときに、

  • どの系譜に属しているのか
  • どんな能力ルールを持っているのか

を調べるようなものです。

こうした見方ができるようになると、自分でクラスを設計するときにも、とても役立ちます。

図でクラス階層の設計を整理する

上部中央の Warrior抽象クラス は、戦士としての共通の土台を表しています。
ここには名前や速度のような共通状態と、共通の考え方が置かれています。

左上と右上の iMovable、iKiUser は、能力の約束を表すインターフェイスです。
これらは「何者か」ではなく、「何ができるか」を整理するルールです。

中央の Saiyanクラス は、Warrior を継承しながら、必要に応じて複数のインターフェイスを実装しています。
下の NamekianWarriorクラス や EarthWarriorクラス も同じように、共通の土台と能力ルールの組み合わせで設計されています。

この図から、抽象クラスは共通の土台をまとめ、インターフェイスは能力の約束を整理し、具体クラスはそれらを組み合わせて実際のキャラクターを表す、という役割分担が見えてきます。

ドラゴンボールで感覚的に整理する

最後に、ドラゴンボールの感覚でまとめてみましょう。

戦士が少ないうちは、1人ずつ個別に考えても何とかなることがあります。
でも、キャラクターが増えてくると、

  • 共通の戦士としての性質
  • 特定の能力を持つ者のルール
  • それぞれの個性

を整理しないと、すぐに複雑になります。

そこで、

  • 抽象クラスで「戦士としての共通の土台」を作る
  • インターフェイスで「飛べる」「気を使える」といった能力の約束を整理する
  • 具体クラスでベジータやピッコロの個性を表現する

という形にすると、クラス階層がとてもわかりやすくなります。

つまり、クラス階層を設計するとは、
ドラゴンボールの戦士たちの世界を、共通ルール・能力・個性に分けて整理すること
だと考えると、とても理解しやすいです。

この感覚がつかめると、抽象クラスやインターフェイスは単なる文法ではなく、大きなプログラムを組み立てるための設計道具だとはっきり見えてきます。