二つ以上のクラスの実装を継承できない |
最後に,複数のクラスの実装を継承できない問題を取り上げる。この問題の原因は,Javaが多重継承を許していないことにある。
Javaでは多重継承ができない。これは,デメリットと言うよりもメリットとして語られることが多い。C++は多重継承を許しているが,その結果プログラムが複雑になり難解なバグを生んだ。
多重継承は実装を引き継げるが複雑
多重継承は,次のような点で混乱を招く。一つが名前の衝突である。同じ名前のメソッドを持つ別のクラスを継承した場合,どちらのメソッドが呼び出されるかわからない(図5[拡大表示])。これは,一つのクラスを継承した二つのクラスを,さらに一つのクラスが継承するときによく起こる。これをダイヤモンド継承という(図6[拡大表示])。一番上位のクラスのメソッドを別々にオーバライドしていて,それを最下層のクラスが呼び出すと問題が起こる。
|
|
クラスの階層も複雑になる。単一継承しかなければ,クラスの構造はツリー状に枝分かれしていくだけだ。これが多重継承となると一気に複雑化する。多重継承を何重も繰り返していけば,クラスの階層関係を把握するのは不可能に近くなる。
Javaは単一継承しか許していないので,このような問題は起こらない。ただ,違うクラスを複数組み合わせて,新しいクラスを作りたいことも当然ある。この解決のために,インタフェースの多重継承という仕組みを用意している。
産業技術総合研究所 情報処理研究部門 主任研究員の一杉裕志氏は,「Javaが単一継承しか許していないのは,プログラミング言語の実装の意味ではいい落としどころでは」と語る。「多重継承の代わりにインタフェースでごまかしたJavaのセンスはとてもいい。ここまでJavaが広まったのは,Javaの言語仕様が適当なところで妥協しているからだ」(東京大学大学院情報理工学系研究科 コンピュータ科学専攻の萩谷昌己教授)との声もある。
インタフェースでは実装が引き継げない
![]() |
図7●Javaでは実装の継承が一つしかできない。 このため,コードを丸ごとコピーしなければならなくなる |
ただしインタフェースによる疑似多重継承には,致命的な欠陥がある。インタフェースでは実装を継承できない。Rubyの作成者であるまつもとゆきひろ氏は「多重継承の代わりにインタフェースを使ったのはJavaの賢いところ。ただし,実装の継承を落としたことはとても痛い」と指摘する。
二つのクラスの機能を引き継ぐ場合,Javaではどちらかのクラスのソース・コードをコピーすることになる。この結果,同じコードがさまざまな個所に分散し,保守性が極端に低下する(図7[拡大表示])。同じ処理をクラスという単位にまとめて保守性を上げようとするオブジェクト指向の利点が生かせない。「マーケティング的には,Javaは大成功を収めた言語だ。しかしプログラミング言語の実装という意味では失敗作だと思う」(まつもと氏)。
Mix-inで実装の継承を実現するRuby
実はまつもと氏が作ったRubyも,Javaと同じく単一継承しか許していない。だが,Javaのインタフェースに相当する部分で実装の継承を実現している。RubyのMix-inという仕組みである。
Mix-inは,再利用性のあるプログラムの機能の部分だけを「モジュール」という単位にまとめ,それを他のクラスが使うというものだ。モジュールはクラスのように,それ自体をnewで生成することはできない。他のクラスに「include」,つまり組み込んで初めて使うことができる(図8[拡大表示])。
Mix-inを使った簡単なRubyのコードがリスト6[拡大表示]である。“test”と出力する機能を持つTestModuleというモジュールを定義している。これをincludeしているのがTestClassだ。これで,TestModuleの持つメソッドを自分のもののように呼び出せる。
ただしRubyのMix-inが解決するのは,多重継承による継承関係の複雑さの部分だけだ。複数のモジュールが同じ名前のメソッドを持っていた場合には多重継承と同じ問題が発生する。Rubyでは,後からincludeしたものが優先される注3)。
|
|
クラスとは別の単位で再利用するMixJuice
Rubyとは別のアプローチで,プログラムの再利用性を高めるMixJuiceという言語も開発されている。Javaをベースにした独自の言語だ注4)。
![]() |
リスト7●MixJuiceのコード。 プログラムが「module」の単位で記述されている。逆にクラスはmoduleによって分割されている |
MixJuiceでも,再利用のためにはクラスとは別の単位を使う。Rubyと同じく,この単位を「モジュール」と呼ぶ。MixJuiceの開発者である産業技術総合研究所の一杉氏は,「Javaなどに見られるクラス・ベースのプログラミングでは,複数のクラスが協調して一つの機能を実現している場合,それが再利用できない。クラスとは別に,再利用の単位があるべき」と言う。
MixJuiceにも,当然クラスはある。しかしプログラミングは,一つのクラスのソース・コードに対してするのではない。協調して動作するクラスをまとめた「モジュール」を単位としてプログラムを記述する。モジュールを再利用して拡張するときは,モジュールに対する「差分」を記述する。
実際のソース・コードがリスト7[拡大表示]である。プログラムが「module」単位でくくられている。クラスは,モジュールの中で「define」キーワードを使って定義されている。この例のように,モジュールを追加することで既存のクラスに対する差分を記述していく。複数のクラスを使って実現される機能を,一つにまとめている。
必然的に実装欠損の問題が起こる
ただし,この方法では必然的に別の問題が発生する。MixJuiceのモジュールを使ってクラスを拡張しようとすると,実装の欠損が起こるのだ。
具体例が図9[拡大表示]である。モジュールm1とm2はどちらもmを継承し,m1はmに定義されたクラスSの派生クラスBを定義している。一方m2は,Sに対してメソッドを追加している。これが普通のメソッドなら問題は発生しないが,派生クラスで実装を強制する抽象メソッドを定義している。実際その派生クラスであるAでは実装を書き加えている。
この二つのモジュールを同時に使おうとすると,リンク時にエラーが発生する。m2がSに追加したメソッドm()を,クラスBが実装していないためだ。これを解決するには,どちらの知識も持った誰かが,これを実装する必要がある。メソッドの追加と,サブクラスの追加という二つの拡張の方向性があることで,生じる問題である(図10[拡大表示])。
|
|
新たな分割の単位を提案するアスペクト指向
MixJuiceのように,オブジェクトを横断した単位でシステムをとらえようとする考え方を,「Separation of Concerns」と呼ぶ。関心事の分離,と訳されることが多い。クラスの相互作用も関心事の一つである。単にモノ(つまりオブジェクト)の単位だけではなく,別の側面からもシステムを分割しようとする考え方だ。
この考え方に,最近注目が集まりつつある。この考え方に基づいたパラダイムの代表格が,アスペクト指向である。アスペクト指向も,関心事を分割の単位にする。複数のクラスに同じ関心事が存在したら,それらを組み合わせて一つの機能を実現する。それがアスペクト,つまり「側面」だ。
具体例が図11[拡大表示]だ。図形を動かして画面を更新する処理を一つのアスペクトとして定義している。図形は,点と線から成っている。点,線それぞれに,移動のメソッドと位置を設定するためのメソッドがある。これらの動作が連携し合って,画面の更新というアスペクトを実現するのだ。
Javaをベースにしたアスペクト指向言語「AspectJ」を使って,アスペクトを記述したソース・コードがリスト8[拡大表示]である。図形の移動という一つのまとまりで,複数のクラスの複数のメソッドを指定している。図形の移動が終わったら,最後に画面を再描画する処理が実行される。
オブジェクト指向とは矛盾しない
アスペクト指向は,オブジェクト以外にシステムを分割する単位を導入し,再利用性や拡張性を高めようとしている。オブジェクトの単位しか分割の軸がなければ,限界があることは確かだろう。
ただし,オブジェクト指向自体が難しいと言われるパラダイムである。それでもここまで普及したのは,「オブジェクトの単位しか分割の軸がない」からだろう。軸が一つだからこそ,どうにか理解できるのである。そこにアスペクトという軸も加わったら,混乱するプログラマは多いだろう。
さらにアスペクト指向は自由度の高いパラダイムである。自由度が高いからこそ,プログラマは困ってしまう。どのようにプログラムを分割し,構築していったらいいのかわからないのだ。つまりある程度自由度の幅を狭め,プログラマにプログラミングの指針を与えることも,プログラミング言語の役割だと言えるのだ。
アスペクト指向は,オブジェクト指向を拡張するパラダイムである。オブジェクト指向の自由度の狭さをよしとする場合は,それを使い続けることができる。アスペクト指向の提唱者である加ブリティッシュ・コロンビア大学のGregor Kiczales氏自身も,「無理にアスペクトを使う必要はない」と語っている。プログラマが自分の開発スタイルに合わせて,使いやすさと拡張性のちょうどいいバランスを見極めながら,こうしたパラダイムを利用していくことになるのだろう。