TL;DR
「クラス」と「クラス定義」は区別して説明するべき。
「クラス」とは「分類」である。
『「クラス」とは「分類」』とすることで、「継承」も説明できる。
はじめに
オブジェクト指向の用語の中でも、とかく「クラス」は喩えられがちです。
まずは「クラス」がどのように喩えられているか、よく見かけるものを挙げてみましょう。
- クラスとは、機械の設計図のようなもの
- クラスとは、料理のレシピのようなもの
- クラスとは、タイヤキの金型のようなもの
なるほど。
いずれも、「何かの作り方」が「クラス」であるという喩えです。
そして「何か=インスタンス」という説明になっていることが多いです。
しかし、私はこれらの喩えに敢えて異を唱えます。
『「インスタンスの作り方」が「クラス」である』と喩えるのは間違っている
より正確にいえば、
『「インスタンスの作り方」そのものが「クラス」である』と喩えるのは間違っている
「クラス」と「クラス定義」を使い分けよう
上記のような喩えって、「クラス」の説明ではなく「クラス定義」の説明だと思うんです1。
そして、「クラス」と「クラス定義」って、(初心者向けの説明では)もっと慎重に使い分けるべきだと思うんです。
当たり前のことを書くと、
- 「クラス」とは
- クラスのこと
- 「クラス定義」とは
- 「クラス」の定義のこと
そして、JavaやC#のようなオブジェクト指向言語におけるプログラミングとは、専ら『「クラス定義」をすること』だと言えます。
「クラス」とは「分類」である
では、「クラス」とは一体何なのでしょうか。
「クラス」とは「分類」のことです。そのまんま。
『2年生は3クラスに分かれている』というときの「クラス」と同じです。
また、『ネコは哺乳類に分類される』というときの「分類」は英語で"class"です。
敢えて何かに喩える必要もないと思ってます。
「クラス」を理解する上で大事なのは、上手い喩えよりも
- 何を、分類しているのか
- どのように、分類しているのか
- 何のために、分類しているのか
ということを理解することです。
「分類」されるのはオブジェクト
オブジェクト指向の世界において2、「分類」されているのはオブジェクトです。
この世界では、
このオブジェクトは、どのクラスに属しているのか
ということが非常に重要です。
「分類」のしかた
ここ、ちょっと回りくどい話になります
現実世界の「分類」は、モノありき
現実世界において、「分類」は既存のモノに対してなされることが殆どです。
神が作った(かどうかは分かりませんが)この世界に存在する様々なモノを、我々人類が「分類」してきたのです。
例えば、目の前に「4本足でニャーと鳴く動物」がいたとします。
このとき、我々がそれを『「ネコ」と識別する』ことができるのは、その動物が既にネコとして「分類」されていることを知っているからです3。
(あまりにも当たり前すぎて、逆に分かりづらい説明になってるかも。それぐらい当たり前なことをいっています)
先に「分類」を「定義」し、後からその「定義」に従うモノを生み出す
現実世界では、(超クリエイティブなスーパーイノベーター以外は)こんなことはしません。
「オブジェクト指向」の世界の「分類」は、定義ありき
翻って、前述したとおりJavaやC#のようなオブジェクト指向言語におけるプログラミングとは、専ら「クラス定義」です。
つまり、プログラミングとは『ある「分類」の概念や特徴を「定義」している』ことに他なりません。
プログラミングの世界では、いわばプログラマーが神なので、モノを生み出すより先に「分類」を「定義」してしまうのです。
Javaでサンプルを挙げてみましょう。
// 「ネコ」とは…
public class ネコ {
// 4本足で、
int getLegCount() { return 4; }
// ニャーと鳴く、
String cry() { return "ニャー"; }
// 動物である
boolean isAnimal() { return true; }
}
愚直なコードですが、これが「クラス定義」であることは間違いありません。
ともかく、ネコを分類する「クラス」が定義できました。
では、「ネコに分類されるモノ」を生み出すプログラムを書いてみましょう。
なお、これ以降はプログラム寄りの記述となるため、「モノ」は「オブジェクト」、「生み出すこと」は「生成する」と書くことにします。
Object x1 = new ネコ();
Object x2 = new ネコ();
Object x3 = new ネコ();
new
演算子にクラス名を渡すと、「そのクラスに属するオブジェクト」が生成されます。また、「○○クラスに属するオブジェクト」は、意味的に「○○に分類されるオブジェクト」と言い換えられます。
つまり、このプログラムを実行すると「ネコに分類されるオブジェクト」が3つ生成されることになります。
ちなみに、1つのクラス定義から複数のオブジェクトが生成できるのは、オブジェクト指向言語の特徴の1つです4。
「インスタンス」とは
ここで、「インスタンス」という用語について整理しておきましょう。
「インスタンス」とは、『「クラス定義」を基に生成された実体』だと説明されます。
先ほどの例でいえば、「ネコに分類されるオブジェクト」が「ネコインスタンス」となります。また、これを単に「ネコオブジェクト」と呼ぶこともあります。
つまり、以下は全て同じ意味です。(上段2つの呼び方することは滅多にありませんが)
- ネコに分類されるオブジェクト
- ネコクラスに属するオブジェクト
- ネコインスタンス
- ネコオブジェクト
「分類」のメリット
「分類」することのメリットについては、ここで多くを説明する必要は無いかと思います。
オブジェクトの「分類」によって得られるメリットは、現実世界でモノを「分類」する際と同様だからです。
個々のモノをそのまま扱うのは大変です。
纏まった「分類」をつくることで、効率よく作業をすることができるようになります。
なぜ「タイヤキの金型」ではダメなのか
ここから本題です
ここまでで、以下を説明してきました。
「クラス」と「クラス定義」を使い分ければ、『「クラス」とは「分類」である』と説明できる
では、冒頭で挙げた以下の喩えでは何がダメなのでしょうか。
- クラスとは、機械の設計図のようなもの
- クラスとは、料理のレシピのようなもの
- クラスとは、タイヤキの金型のようなもの
実は、これらの喩えでも「クラス定義」に関しては、問題なく説明することができます。
しかし、これらの喩えでは「オブジェクト指向」のある概念を上手く説明できません。
上手く説明できない概念とは、クラスの「継承」です。
「継承」とは
クラスの「継承」そのものの解説については、バッサリと割愛します。
階層構造と「継承」の説明
「オブジェクト指向」における「継承」の説明では、以下のような階層構造がよく用いられます。
- 動物
- イヌ
- プードル
- ネコ
- ミケネコ
先ほど挙げた「ネコのクラス定義」をベースに、階層構造に従うようにコードを少し変更します。
// 「動物」とは…
public abstract class 動物 {
// 「足の数」を取得でき、
abstract int getLegCount();
// 「鳴く」ことができる
abstract String cry();
}
// 「ネコ」とは…「動物」に分類され、
public class ネコ extends 動物 {
// 4本足で、
@Override int getLegCount() { return 4; }
// ニャーと鳴く
@Override String cry() { return "ニャー"; }
}
まずは、復習をかねて「ネコオブジェクト」の説明をしてみましょう。
- 「ネコオブジェクト」とは、「ネコのクラス定義」を基に生成されたオブジェクト
- 「ネコオブジェクト」とは、「ネコクラス」に属したオブジェクト(上記と同義)
- 「ネコオブジェクト」とは、ネコに分類されるオブジェクト(上記と同義)
次に、「サブクラス」の説明をしてみましょう。
- 「ネコ」は、「動物」に分類される
- 「ネコクラス」は、「動物クラス」のサブクラス(上記と同義)
- 「ネコ」は、「動物」のサブクラス(上記と同義。簡略化した言い方)
- 「ミケネコ」は、「ネコ」に分類され、必然的に「動物」にも分類される
- 「ミケネコクラス」は、「ネコクラス」のサブクラスであり、必然的に「動物クラス」のサブクラス(上記と同義)
- 「ミケネコ」は「ネコ」のサブクラスであり、必然的に「動物」のサブクラス(上記と同義。簡略化した言い方)
さらに、インスタンスの説明をしてみましょう。
- 「ネコインスタンス」は、「ネコクラス」のインスタンス
- 「ネコインスタンス」は、「動物クラス」のインスタンス
- 「ミケネコインスタンス」は、「ミケネコクラス」のインスタンス
- 「ミケネコインスタンス」は、「ネコクラス」のインスタンス
- 「ミケネコインスタンス」は、「動物クラス」のインスタンス
- 「ミケネコインスタンス」は、「ネコクラス」のインスタンス
『「クラス」とは「分類」である』という説明で、十分に上記の説明がつくことがお分かり頂けるでしょうか。
「クラス」を「タイヤキの金型」で喩えていると、この階層構造の説明ができないのです。
というわけで、最後にもう一度
「クラス」とは「分類」である
補足
「継承」よりも、「特化」と「汎化」
どうも「継承」は、「コードの再利用のため」と説明されがちです。確かに継承がコードの再利用につながることはよくありますが、それは継承の副作用にすぎません。
しかし、初心者が書くコードには「コードの再利用だけを目的とした、誤った継承5」が出てくることがあります。
これは、上記のような説明が一因ではないでしょうか。
そこで、「継承」よりも、「特化」と「汎化」を強調したいです。
一応、書いておくと、
- 特化
- 動物 →(特化)→ ネコ →(特化)→ ミケネコ
- 汎化
- 動物 ←(汎化)← ネコ ←(汎化)← ミケネコ
ですね。
「特化」と「汎化」でextends
を説明すれば、「再利用のための継承」のような誤解は生じにくくなるのではないでしょうか。
おわりに
調子に乗って、2本目のマサカリです。
1本目はこちら→やはり「オブジェクト指向」のオブジェクトを「モノ」とかって訳すのは間違っている。
もっとコンパクトな記事を書けるように精進したいです。