導入
オブジェクト指向のプログラミング言語の機能の中でも目玉と呼べるものが「継承」です。しかしながら継承によって発生する可能性があるデメリットが大きいため、Java言語やProcessingでは使用方法に制限が加えられています。前回は、継承で行うことができる機能を部分的に実現する方法であるインターフェイスを学びました。今回は、継承を用いずに継承で行うことができる機能を実現するもう1つの工夫、委譲(いじょう)を紹介します。
展開
必要な機能を持っているクラスを利用する仕組みが継承と委譲
継承や委譲を使う基本的なストーリーは次の2通りが考えられます。
どちらも複数のクラスの間に、包含関係や依存関係を上手に設定することを目的としています。上手に設定することでコードの見通しが良くなり、その結果つくりやすく、メンテナンスのしやすいコードになるのです。
単純に継承したクラスと、著作者名情報を取得するメソッドを追加したクラスをつくる
これから紹介するサンプルコードは、先ほど紹介したストーリーが両方適用されたものです。 連載第16回ではインターフェイスを活用して設定ファイルの情報を読み込むクラスをつくりました。 連載第16回演習2の解答例で掲載したコードを、インターフェイスを使わず継承を利用して再作成してみます。解答例のクラスから設定情報を読み込むコードをクラスに切り出しConfigInfo
クラスとします。そしてこれを継承したAppInfoLoader8
クラスを作成します。さらに新しいクラスAppInfoLoader9
には著作者情報を持たせ、それを取得するメソッドを付加します。これらをテストするスケッチをTestAppInfoLoader6.pde
とします。
以下のファイルはTestAppInfoLoader6
フォルダに納めてください。CONFIG.TXT
とSETTING.TXT
はさらにその中のdata
フォルダに納めてください。
「インターフェイスの実装」と「クラスの継承」の最も大きな違いでありメリットは、クラスの継承の場合はスーパークラス側で実装済みのメソッドを利用できることです。AppInfoLoader8
クラスはコンストラクタとgetClassName
メソッドしか持ちません。それにもかかわらず、スーパークラスであるConfigInfo
クラスのメソッドを呼び出して使うことができます。目的の仕事をするメソッドをスーパークラスが持っていれば、サブクラスは特にコードを追加すること無く利用できます。これは便利な仕組みです。
さらに追加の機能が欲しくなった場合、新しいメソッドをサブクラスに書き込みます。クラスAppInfoLoader9
では、クラスConfigInfo
を継承した後に著作者名を取得するための新しいメソッドgetAuthorName
を追加しています。
プロトタイピング中など、コードが発展途上にある場合や、そもそもどんなことができるか不明瞭な場合は、このように継承したクラスを発展させていき、やがて重要なメソッドが明らかになればスーパークラスのConfigInfo
クラスへ吸収し、ConfigInfo
クラスを継承するクラスを再構成するのです。
緻密な設計を必要とするクリティカルなシステムのコードではこんな行き当たりばったりな方法論は通用しませんが、私たちが今利用している言語はProcessingです。アートやプロトタイピング、そして本来は教育に適した言語です。思う存分行き当たりばったりにコーディングしましょう。やがて勘がつかめれば、洗練された設計を行うスキルを効率良く身につけられるはずです。今、Processingを使う私たちは、それとは別の「作品作り」に注力しましょう。
継承した後、今あるメソッドをオーバーライドする
クラスConfigInfo
とクラスAppInfoLoader8
やAppInfoLoader9
を比較すると、同じ名前のメソッドgetClassName
が定義されています。
このように、サブクラスでスーパークラスと同じメソッドを再定義することをオーバーライドと呼びます。 クラスConfigInfo
を単体で使用する際には、getClassName
メソッドでクラス名を取得すると、クラスConfigInfo
で定義されている定数CLASS_NAME
の値ConfigInfo
が取得されます。
クラスAppInfoLoader8
を使用する際には、オーバーライドしたgetClassName
メソッドでクラス名を取得すると、クラスAppInfoLoader8
で定義されている定数CLASS_NAME
の値AppInfoLoader8
が取得されます。 2つのクラスを使うコードの側から見ると、至って自然な挙動です。メソッドを「上書き」したわけですから、サブクラスのインスタンスを利用しているのに、スーパークラス側のメソッドが優先されては困ります。クラスAppInfoLoader8
のインスタンスのスーパークラスのクラス名が必要ならば、クラスAppInfoLoader8
内でsuper.getClassName()
と呼べば、スーパークラスのクラス名が得られます。
継承した後、今あるメソッドをオーバーロードする。すなわち引数が異なるメソッドを作成する
クラスConfigInfo
にはloadConfigInfo()
メソッドがあります。これは設定ファイル名を引数として受け取るように宣言されていますから、設定ファイル名を指定しなければ使うことができません。しかし、場合によってはファイル名を指定しないで呼び出し、その際にデフォルトの設定ファイルを読み込むということも可能にしたいかもしれません。その際には、引数を持たないメソッドを定義できると便利です。そこで、サブクラス側で引数を持たないloadConfigInfo
メソッドを定義します。次のsketch TestAppInfoLoader7
を見てください。
以下のファイルはTestAppInfoLoader7
フォルダに納めてください。CONFIG.TXT
はさらにその中のdata
フォルダに納めてください。
次のコードのように、スーパークラスにあるメソッドと同じメソッド名でありながら、引数が異なるメソッドを定義することをオーバーロードと呼びます。
そして、このオーバーロードによって、呼び出し側では引数を持たないメソッドとして呼び出しが可能になりました。
こうなると、スーパークラスに定義されたメソッドと引数において一致しないメソッドを呼ぶわけですから、クラスAppInfoLoader10
への参照をクラスConfigInfo
に代入して、AppInfoLoadr10
でオーバーロードしたメソッドを呼んでも使用できません。このためクラスTestAppInfoLoader
内のtestAppInfoLoader
メソッドの引数を変更しなければなりませんでした。継承を利用していて、メソッドの追加を行った場合の弊害です。この弊害を防ぐためには、メソッドの追加が必要ないように緻密な設計を行っておくか、そもそも継承を利用しないでおくかとなります。
継承を使わず委譲で実現する
これまでで継承と、それに付随するオーバーライドやオーバーロードを学習しました。同様な仕事をもっと泥臭く実現する方法があります。次のコードを読んでください。AppInfoLoader11
は継承を使わずにこれまでと同等の機能を持たせています。
以下のファイルはTestAppInfoLoader8
フォルダに納めてください。CONFIG.TXT
とSETTING.TXT
はさらにその中のdata
フォルダに納めてください。
クラスConfigInfo
のコードはそのままです。ただし、クラスAppInfoLoader11
はそれを継承していません。クラス定義の先頭が
ではなく、単に
となっています。せっかくある継承の仕組みですが、それを使わない選択肢もあるということです。では、どのようにして同じような機能を実装しているかと見ると、クラスAppInfoLoader11
の内部にクラスConfigInfo
のインスタンスを持たせています。必要に応じてAppInfoLoader11
がConfigInfo
に仕事をたらい回しし、結果をさも自分が行った仕事のように処理します。 このように、自分ではない別のクラスのインスタンスに仕事を「譲って」「委せる」ことを委譲と呼びます。継承をサポートしないプログラミング言語で使われていた方法に付けた名前で、泥臭いのですがその分自由が利きます。継承を使わなくても、多重継承できなくても、必要なクラスのインスタンスを必要な数だけ持てば良いのです。オーバーロードもオーバーライドもプログラマが自分で手配して組み上げるのです。先祖帰りのようで抵抗があるでしょうか。しかし無理な継承関係を作り込むぐらいならば余程分かりやすいコードが書けます。特にProcessingのように小規模なソフトウエア向きの言語では、継承関係を検討しながら緻密に組み上げるよりは適宜委譲しながら「とりあえず動く」コードを割り切って手早く組み上げるほうが良いでしょう。
欠点と言えば、継承を用いる場合に比較して、委譲するためのコードをタイプする量が増えること。このため委譲の使用に関して意見の分かれるところではあるのですが、大変有効な工夫です。
Java言語やProcessingではインターフェイスの仕組みがあり、インターフェイスは1つのクラスで複数実装できますから、統一した実装を強制する手段としてインターフェイスを用い、中身では適切なメソッドを持つクラスに委譲する方法が用いられます。
現実的な採用順
プロトタイピングに始まって完成に至るまでのプログラミングの過程において、当初から緻密な設計が可能であれば別ですが、そもそも何をどうつくって良いか分からないような場合が多いものです。
そのような場合には、最初からクラス構成をきっちり設計してからコーディングに入ることができません。まずは「こんな感じかな」というクラスを書き、コードが大きくなってきたら機能や領域ごとのクラスに切り分けていくことになるでしょう。そこでは委譲が用いられます。やがてさらに対象が明らかになるにしたがって、必要なインターフェイスが見いだされるでしょう。いよいよ本格的に問題が理解され、組むべきコードの構成が分かったら、必要に応じてスーパークラス、サブクラスと言う継承関係を設計し、そのようなコードに組み替えて行く。このような採用順が現実的な小規模プログラミングの過程です。
演習
演習1(難易度:easy)
高校生の成績を保持するクラスを作成しましょう。氏名、性別、国語、数学、英語のtテストの点を保持し、取得するメソッド、平均点を取得するメソッドを備えるクラスBase
をまず作成しましょう。その後に、理系を選択した生徒のためにBase
を継承したクラスScience
、文系を選択した生徒のためにクラスHumanities
を作成してください。Science
は物理、化学のテストの点を保持、取得するメソッドを持ち、Humanities
は古文、社会のテストの点を保持、取得するメソッドを持たせてください。
演習2(難易度:easy)
演習1の問題において、クラスHumanities
は継承を用いず委譲を用いて書き直しましょう。
まとめ
- 継承、オーバーライド、オーバーロードという仕組みや、委譲という工夫を学びました。
学習の確認
それぞれの項目で、Aを選択できなければ、本文や演習にもう一度取り組みましょう。
継承、オーバーライド、オーバーロードが理解できましたか?
- 理解できた。気持ちよく納得した。
- 理解できた。しかし、今ひとつスッキリしない。
- 理解できない。
- 委譲の意味と役割が理解できましたか?
- 理解できた。気持ちよく納得した。
- 理解できた。しかし、今ひとつスッキリしない。
- 理解できない。
参考文献
- 『Java言語プログラミングレッスン(下)』(結城浩 著、ソフトバンクくりエィティブ株式会社)
- Java言語のオブジェクト指向的特徴を大変分かりやすく解説した入門書中の名著。上下巻ともにJava言語入門者は必携。
演習解答
- 以下のファイルをsketchフォルダ
TestResults
に納めます。
- 以下のファイルをsketchフォルダ
TestResults2
に納めます。