はじめに
オブジェクト指向という概念は、javaを使う者としては必修課目と言っても過言ではないほど重要な概念です。
すべての要素を、オブジェクトとして定義することで、プログラムをオブジェクト同士の結び付きのみで表現することができます。
この概念は、現実世界の業務をモデリングするときに非常に有効な概念です。よく犬や猫、はたまた自販機などになぞらえて例えられているものがありますが、こういったものにさえ適用出来てしまうのがオブジェクト指向の利点と言ってよいでしょう。
今回は、理解しやすそうに見えて理解しづらい、オブジェクト指向についてまとめてみました。
本稿の対象
自力でプログラムを作れるが、オブジェクト指向がいまいち理解できない、という方を対象としています。
もし、プログラムがまだ組めない、というレベルなら、オブジェクト指向はまだ理解しなくても構いません。まずはjavaで何が出来るかを学習してみてください。
なぜオブジェクト指向が理解しづらいのか?
「たとえ話」の罠
数ある入門書や、ネット上に溢れているドキュメントの多くは、オブジェクト指向を現実世界になぞらえて解説しています。
動物インターフェースを実装したものが犬スーパークラスで、それを継承したものが柴犬や秋田犬です。犬スーパークラスは「吠える()」メソッドを持ち…
その通りです。これこそオブジェクト指向ですね。しかし、この説明で分かるのは、オブジェクト指向を既に理解している人だけです。
これは当然の話で、例えば業務プログラミングを組むときに、犬や猫がどうして出てくるでしょうか。仮にペットショップで扱われる犬や猫を対象としたクラスがあるとしても、そのクラスが吠えたり鳴いたりすることはありません。それであれば、MVCモデルでも教えていた方が幾らか実用的です。
このように、オブジェクト指向のたとえ話は、概念を上手く説明したにすぎず、それを具体的にどう使うかまでは説明されていません。
ハサミの使い方を教えるときに、ハサミの概念を教えているようなものです。
オブジェクト指向という道具の使い方は、設計を担うエンジニアは必ず熟知していなければなりません。しかしながら、設計手法は多岐に渡る上に、システムにも多数の種類があるため、こちらの解説も抽象的にならざるを得ないのが現状です。
とはいえ、抽象的な解説をいくらされても理解できないものは理解できません。そこで、理解への手助けとなるような、良質な教材を考えてみます。
ソースコード
幸いなことに、javaはオープンソースで書かれていますので、jdkのソースコード同梱版をインストールすれば、src.zipというファイル名で同梱されています。
このソースコードは、普段の実務に即したものではないかもしれませんが、日常的に、高い頻度で使っているはずです。その実装を理解するということは、オブジェクト指向を理解するに等しいでしょう。
ひととおり読み終える頃には、犬猫の難解なたとえ話も、なんとなく理解出来るようになっているはずです。
入門として
最初は何でも構いません。「オブジェクト」が何なのか知るためには、java.lang.String
クラスがよいでしょう。インターフェースを理解したければ、java.util.List
、java.util.ArrayList
などを読みます。自分がよく使うクラスでも構いません。たとえば、StringBuilderクラスなどもお勧めです。
以下にいくつかご紹介します。
java.lang.String
Stringクラスは、大抵の入門書では、「型」としてintなどのプリミティブ型と同様に、ただの文字列型として紹介されてきました。これがchar型の配列を持つオブジェクトであるということは、ソースコードを読んだことのない初心者にはあまり知られていません。
”で囲まれた文字列を、文字列リテラルといいます。これはjavavmによって評価され、Stringオブジェクトのインスタンスを暗黙的に生成します。
つまり、以下のコードは等価です。
String str = "";
String str = new String();
ところで、"abc".equals("String")
という、ある意味独特な記法がありますが、この理屈も上記を前提にすれば分かりやすいですね。
123.toString()
が使えないことも併せて確認しておきましょう。123はint型であり、オブジェクトではありません。
プリミティブ型との決定的な違いをここで認識してください。
この、「プリミティブ型」という概念があることによって、javaは純粋なオブジェクト指向言語ではない、という論調もありますが、詳しいことは主旨に反するため、ここでは控えます。
char[]というフィールドを持ち、各種メソッドを持つStringクラスは、「オブジェクトとは何か」を学ぶのに最適ではないでしょうか?
ListとArrayList
Listインターフェースは、add()をはじめとするメソッドが定義されています。これは、使用者がそれを「何のリストであるか」を意識しなくても使えるように配慮したものです。
さて、普段Listを使うとき、型はListとして宣言するものの、そこにはArrayListなどの実装されたクラスをインスタンス化して代入しているはずです。
当然のことですが、Listはインターフェース、つまりただの設計図ですので、インスタンス化は出来ません。代わりにArrayListをインスタンス化します。
Listクラスの具象クラスには、前述したArrayListと、LinkedListがありますが、利用者はListオブジェクトとして受け取ることで、それがArrayListであろうがLinkedListであろうが、同じリストとして使えるということに着目してみてください。
インターフェースに関しては、以下の記事でも詳しく取り上げています。よろしければご覧ください。
カプセル化の概念
よく耳にする「カプセル化」という言葉があります。これは単純なことですが、例えば、java.lang.Integer
クラスのparseInt
メソッドを使うとき、その実装を知る必要はありません。なぜなら、APIの利用者は、parseIntを呼び出した結果のintが欲しいわけですから、内部的に何が行われていても関係ありません。
また、先ほど挙げたStringクラスでも、内部的にchar型の配列があるということを知る必要はありませんし、private宣言されているため触ることができません。
もし、Stringクラスの内部的なフィールドであるcharの配列を自由に弄られたとすればどうでしょうか。StringクラスがImmutableであるという前提は崩壊し、途端に何が入っているか分からない箱になるわけです。
JavaBeansがgetter/setterというアクセサを持ち、肝心のフィールドがprivateで隠蔽されているのは、内部のフィールドへのアクセスに制約を持たせることで、安全性を担保するためのものです。
setterにバリデーション機能を持たせることで、入力される値を制御するのは有名な例ですね。
ポリモーフィズムという概念
まさに前述したjava.util.List
を始めとするインターフェースがそれにあたります。
多態性、などと訳されていますが、要するに、メソッドを定義し、それを複数のクラスで継承または実装して使える、という概念です。
例えば、Objectクラスに定義されているtoString
メソッドがそれでしょう。全てのクラスはObjectを継承しているため、各クラスに必ずあるメソッドですが、当然、何かクラスを作ったとして、toStringメソッドをオーバーライドしないまま使っても期待した結果は得られません。たとえばInteger型をそのままString型に変換することはできませんので、何らかのロジックを経ねばなりません。Listにしても、StringBuilderにしてもそうです。
特殊な用例として、「そのクラスが何かを問わず、toStringされた結果だけほしい」という場合は、Object型で宣言された変数をtoStringしても良いわけです。中身が何であっても関係ありません。
このように、単一のメソッドを多数の実装に擬態することができる、というのがポリモーフィズムの概念です。必ず覚えなくてはいけないかのように紹介されていることが多いですが、コードを書くうちに自然と理解できますので、先のカプセル化も含め、最初は「そんなものがあるんだ」程度の認識で良いと思います。
最後に
オブジェクト指向とは、単なる便利な道具です。判断によっては、あえて使わないという選択肢すらあります。関数型言語の普及状況を見るに、純粋なオブジェクト指向言語が無くなる日も近いのかもしれません。
しかし、オブジェクト指向に限らず、設計に関わる知識は、いずれ有力な手助けとなるはずです。もしオブジェクト指向言語が無くなってしまったとしても、その設計手法は、何らかの形で役に立つことでしょう。
本稿が、そういった時の手助けになれば幸いです。