クラス・インターフェースは REALbasic の持つとても有用な機構のひとつである。この機能は、多重継承の持つ複雑さを回避しつつ、その利点を提供するものとなっている。
クラス・インターフェースに関する REALbasic のドキュメントは十分であると言い難いが、クラス・インターフェースという概念自体は REALbasic に固有のものではなく、オブジェクト指向言語一般のものであるから、本質的な意味で情報不足なわけではない。こうした経緯を踏まえ、本稿ではクラス・インターフェースに関して、REALbasic に特化した部分に焦点を当てて紹介していきたい。願わくば、この試みが成功せんことを祈りつつ。
REALbasic で変数やプロパティを宣言する際に型を指定する必要がある(これを怠ると、コンパイラの小言をくらうことになる)。これは SmallTalk や他の多くのスクリプト言語で、「動的な型付け」と対比して「静的な型付け」と呼ばれている。しかし型とはいったい何だろう。型とはクラスのことではない。型というのは、大なり小なり、そのオブジェクトが何らかのメソッドを持つ(あるいは何らかのメッセージを受け付ける)ことを宣言するものである。あるオブジェクトはデフォルトの型を持ち、その名前はそのオブジェクトの持つメソッド全てをまとめたものとなっている。--- すなわち、デフォルトの型名とは、そのオブジェクトのクラス名と同じである。そのオブジェクトは他の型としても宣言可能であるかもしれない。換言すれば、型とは、あるクラスによって実装されている様々なインターフェースのことである。
型付けは、「型」の定義は何かという問題も含めて、コンピュータ科学の研究者達にとっては果てしない議論の材料となるものである。しかし、ここで我々に必要なのは、REALbasic での扱い方だ。型は確かに存在するし、オブジェクトは何らかの型を持つし、そして我々は型を宣言したり、新しい型を自分で作ったりしなければならないのだから。
REALbasic でオブジェクトに対して複数の型を定義しようと思ったら、クラス・インターフェースを使えばよい。クラスの異なるオブジェクトが、同一のクラス・インターフェースを持つこともあり得るし、その場合必要に応じて、同一の型を持つオブジェクトとして扱うことが可能である。後に示す例を見ればおわかりいただけるが、これは非常に有益な機能である。
クラス・インターフェースは、あるオブジェクトが実装しなければならないメソッドのリストというだけのものである。異なるクラスのオブジェクトは、そのメソッドを異なる方法で実装するかもしれない。これにより、インターフェースの多態性がもたらされるのである。これを一言で言えば、「型はインターフェースを語り、クラスは実装を語る」となる。(訳注: 原文は "types are about interface, classes are about implementaiton")
以下にクラス・インターフェースの簡単な例を示してみよう。
クラス・インターフェース Serializable は、fromString(s as String)
と toString as String
という 2つのメソッドを持つ。
例えばネットワーク越しに送信したい場合など、様々なオブジェクトを String として表現したいことがあるだろう・・・Serializable に仕掛けられたアイディアの背景はここにある。オブジェクトを送信するために Socket に渡す場合には、何らかの方法でオブジェクトを String に変換する必要があるわけだ。しかし、String に変換可能な全てのクラスのリストをうまいこと管理し続けるのは、明らかに現実的な方法ではない。その変わりに、ネットワーク越しにやり取りされるオブジェクトには、全て Serializable インターフェースを実装することにすればよいのだ。Socket は各クラスがどういう方法でそれを実装するのかを知っている必要はなく、ただそのオブジェクトに toString
メッセージを送れば String を取得出来ることだけを知っていればよい。Socket は SendObject(theObj as Serializable)
というメソッドを次のような方法で実装出来る。
Function SendObject(theObj as Serializable) as Boolean dim s as String s = theObj.toString Return me.SendString(s, destination) End Function
クラス・インターフェースなしに、これをやろうとおもったら、ちょっと考え込んでしまうだろう。
クラスがあるインターフェースを実装することの宣言は簡単にできる。当該クラスのプロパティ・ウィンドウを開き、"Interfaces" 欄にその名前を記述すればよい。複数のクラス・インターフェースを書くときには、コンマで区切る。
Serializable インターフェースを Foo クラスに実装するには、Foo に fromString
と toString
というメソッドを Serializable に宣言されているのと同じ引数で定義してやればよい。
クラス・インターフェースは継承されるのか否か。RB-NUG に寄せられた Joe Strout 氏と Matt Neuberg 氏の例を借りて、クラス・インターフェースと継承に関して見ていくことにしよう。(訳注: RB-NUG とは REALbasic Network Users Group のことで、いわゆる「本家メーリングリスト」のこと。詳しくは http://lowendmac.com/lists/realbasic.shtml を参照)
まずは下準備から。
メソッド Foo.WhoAmI
は「I'm a Foo!」を返し、メソッド SubFoo.WhoAmI
は「I'm a SubFoo!」を返すものとする。
ここでメソッド
Sub Test1 dim a as Foo a = new Foo msgbox a.WhoAmI EndSub
は「I'm a Foo!」を表示し、メソッド
Sub Test2 dim a as SubFoo a = new SubFoo msgbox a.WhoAmI EndSub
は「I'm a SubFoo!」を表示する。また、次のメソッドでも同様だ。
Sub Test3 dim a as SubFoo a = new SubFoo msgbox Foo(a).WhoAmI EndSub
しかし、以下のメソッドでは「I'm a Foo!」が表示されてしまう。
Sub Test4 dim a as SubFoo a = new SubFoo msgbox Test_Interface(a).WhoAmI EndSub
ところが、さらに続けてクラス SubFoo に Test_Interface を実装してみると、次のメソッドは「I'm a SubFoo!」を表示するのである。
Sub Test5 dim a as SubFoo a = new SubFoo msgbox Test_Interface(a).WhoAmI EndSub
問題は、一見クラス・インターフェースが継承されたように見えるのに、それに矛盾する挙動があることだ。もしクラス・インターフェースがサブクラスに継承されるのなら、Foo(a).WhoAmI
は Foo に実装されたメソッドを呼び出すはずだ(RBTDG の 125ページによれば、REALbasic 1.0 ではこう動作していた)。それがクラス Foo のデフォルトのインターフェースであるのだから。しかし、Test3 は Foo(a).WhoAmI
が「I'm a SubFoo!」を表示することを示している。(訳注: RBTDG とは Matt Neuberg 氏の著書 "REALbasic The Definitive Guide" のこと。氏の Webページは http://www.tidbits.com/matt/)
その一方 Test4 では、Foo が Test_Interface を実装し、SubFoo はしていない場合、Test_Interface(a).WhoAmI
がコールされたら SubFoo の継承関係を逆にたどって Test_Interface が実装されたところまで探索し、見つかったところでそのメソッドを実行することを示している。この機構は、通常の探索順序とは反対のやり方の、クラスへのメッセージ送信方法を提供するものである(クラスへのメッセージ送信に関しての議論は RBTDG の 125ページを参照のこと)。
最後に Test5 は SubFoo でもTest_Interface を実装していた場合には、Foo.WhoAmI
が SubFoo.WhoAmI
でオーバーライドされることを示している。
いずれにせよ、継承/型キャスト/インターフェースの相互作用について、その正確な性質を理解したいのなら、読者諸兄自身が様々な状況のテストコードを書いて、動かしてみることをお勧めする。
最後に、クラス・インターフェースが有効な場合をいくつか提示してみようと思う。もちろんこれが全てではないので、追加できそうな面白い例があれば歓迎する。
クラス・インターフェースの興味深い活用方法の一つに、標準的なインターフェースを与える、というものがある。例えば、プログラムの中でキューを使う必要がでたとしよう。キューを実装する方法は沢山ある。配列を使ってもいいし、リンクリストを使っても良いだろう。しかし、配列版キューに依存したコードを、途中からリンクリスト版キューを使ったものに書き直すのは、面倒なことになるだろう。あるいは、状況に応じて違う実装方法を用いたい場合もあるかもしれないが、こういう時に常に正しい型をうまく参照するのはちょっとばかり管理コストのかかることでもある。こういった問題は、クラス・インターフェースを使えば解決できるのだ。
スタックの実装の際には必ず Queue インターフェースを実装するように心がければよい。こうしておけば、もしある時点で実装を切り替えたくなった場合でも、プロジェクトのコードの中から、スタックのコンストラクタ呼び出し部分を検索して、"new Array_Queue" の箇所を全部 "new LL_Queue" に変えるだけですむ。
Java のコレクション・フレームワーク(私はこいつで勉強した)ではこの方法が採用されており、Collection・Set・List・Map という4つの基本インターフェースが定義されている。
コンストラクタを持つクラス Foo があると仮定しよう。そしてこの時、Foo のメンバ・メソッド内で何らかのオブジェクトを Foo へキャストする必要があるとする。すると、REALbasic は Foo のコンストラクタが呼び出されたものと解釈し、エラーを投げるのである。この問題を回避するためには、Foo のメソッド全てを宣言した Foo_Interface というインターフェースを定義し、それを Foo に実装させる。そして、オブジェクトは Foo へのキャストではなく、Foo_Interface へのキャストにしてやると、コンパイラに対する曖昧さがなくなるのでうまくいく、という寸法だ。
前述の Serializable インターフェースにより、本質的に機能の異なる一群のオブジェクトに対して、簡単な方法で共通の操作を行えるようになる。例えば、Erich Rast 氏の Storable Objects クラスにその実践的な例をみることが出来る。
オブジェクトのリストをソートしたくなることはよくあるが、これには 3つのやり方がある。最初のアプローチは、状況にあわせてその都度ルーチンを書くことだ。いくつかの理由でこれはお勧めできないやり方である。例えば、並び順が安定するソートを行いたいだとか、降順の場合も扱いたいだとかいうことになると特にそうなのだが、ソート・アルゴリズムを実装するのは結構難しいものであるから、そんなものをいつもコーディングし直していたらバグを混入させる可能性がそれだけ高くなる。2つ目は、REALbasic の配列がそうであるように、自分の設計したコンテナ・クラスにソート・ルーチンを付けておく、というものだ。これは 1つ目のアプローチよりはベターだが、柔軟性に欠けるという問題は抱えたままだ。状況によっては、あるソート・アルゴリズムの代わりに、別のアルゴリズムを用いたい場合もあるだろう。これを全てオーバーライドで片付けるとすると、コンテナのサブクラスが増え続け、結局 1つ目の方法と同じになってしまう。
この問題に対してクラス・インターフェースを使えば、低コストで十分な柔軟性と信頼性を得ることができると言ったら驚くだろうか。しかしこれは、ここまでに書いてきたアイディアのバリエーションなのである。まず、実装したいアルゴリズムを使って、ソート・ルーチンをモジュールの関数として実装する。この際、ソート対象の型は Sortable としておく(例えば、関数に対しては Sortable 型の配列を渡すようにする)。すると、Sortable クラス・インターフェースに必要になるであろうメソッドを決定できるだろう。
Thomas Tempelmann 氏はこのアプローチを使ったクラスとクラス・インターフェースのセットを書き、公開した。そしてこれこそが REALbasic でクラス・インターフェースをうまく使った例として、私が最初に出会ったものだったのである。是非、皆さんにもこのプロジェクトをダウンロードしてみてほしい。そこでは、異なるクラス・インターフェースを使い、2つの実装例が示されている。
ここで今一度、Java のコレクション・フレームワークの仕様を参照することをお勧めしたい。この場合、Java ではコンテナとアルゴリズムの分離をどうやって実現しているのかを見ていただきたいのだ。
一般的に言って、イベントとはオブジェクト間の通信に使われる何らかの機構のことだ。一番簡単な例は、あるオブジェクトが別のオブジェクトのメソッドを呼び出すことである。あるメッセージの送信オブジェクトと受信オブジェクトが、お互いについてあまり知らなかったり、なんにも知らなかったりすると、ことはもう少し複雑だ。
「イベント・パターン」(他に適切な言葉を思いつかなかった)の一つに、コールバックがある。コールバックは、あるイベントが発生したことを別のオブジェクトに伝える場合に、この伝達を可能にするちょっとした情報を呼び出し側オブジェクトに予め仕込んでおくことで実装される。C プログラマなら、これを実装するために関数ポインタを使うだろう。しかし、REALbasic ではその方法は使えないので、代わりにオブジェクト間の通信を可能にする仕組みとしてクラス・インターフェースを使う。
REALbasic でコールバックを実装するのに必要な一般的な手順は以下の通り。まず、オブジェクトは通知者か受信者のどちらかになる。受信者オブジェクトは OnEvent(parameters as Object)
のようなメソッドをもつ EventReceiver インターフェースを実装しなければならない。通知者オブジェクト側には、AddEventRedeiver(receiver as EventReceiver)
と RemoveEventReceiver(receiver as EventReceiver)
メソッドをもつ EventNotifier インターフェースを実装する。受信者オブジェクトでは、あらかじめメッセージ送信先リストに追加するように依頼しておくと、通知者オブジェクトにイベントが発生した時に通知がくる、という流れになる。
REALbasic には同じウィンドウ内にあるコントロール間でのコールバックを、最初からいくつか組み込んであり、オブジェクト・バインディングと呼ばれている。オブジェクト・バインディングに関してはあまりドキュメントが多くないので、私は別稿で取り上げるつもりだ。しかし、コールバックやその他様々なイベント処理はウィンドウ内でのコントロール間通信の利用にとどまらない。そのよい例が SAX だ。SAX とは、イベント・ドリブン方式で行う XML プロセッシングのインターフェースである。例えば 50MB の XML 形式のデータファイルがあったとしよう。これを丸ごとオンメモリに読み込んで、解析して、木構造として保持しておくのは現実的ではない。例えば、そのようなファイルから「オタワ」という語を含むレコードだけを取り出す方法について考えてみて欲しい。そして、SAX を使ってこれを行う方法を探してみて欲しい。(訳注: オブジェクト・バインディングに関する「別稿」は、本翻訳作成時点ではまだ存在しないようである。また、原文では特に触れられていないが XML 形式を木構造で保持する標準としては DOM がある)
あるオブジェクトと EdiField をバインディングして、EditField のテキストが変更された場合に通知がされるようにしたいことがある。あいにくのことながら通常は、あらかじめオブジェクトの種類を知ることは期待できない。そこで、通知を受けたいオブジェクトは TellMeWhenYourTextChanges メソッドを持つようにすれば、残りの解決方法がはっきりしてくる・・・すなわち、EditField は通知を受けたいオブジェクトのリストを持っておき、TextChange イベント・ハンドラでこれらのオブジェクトに対して通知メッセージを送ってやればよいわけだ。通知を受ける側のオブジェクトは TellMeWhdnYourTextChanges メソッドに相当するもの(もうちょっとマシな名前を付けよう)を持つクラス・インターフェースを実装することになる。
そこで、TextChange イベントの通知を受けたいオブジェクトが実装すべきクラス・インターフェースを定義してみよう。
次に、TextChangedNotifier クラス・インターフェースを定義する。このインターフェースを EditField のサブクラスが実装することで、TextChange イベントの通知を受けたいオブジェクトの受け付けが出来るようになる。こうしておけば、半年後に誰か(あなた自身を含めて)がコードを読むことがあっても、どの EditField が TextChange イベントの通知機能を備えているのかということが一目瞭然だろう。
このクラス・インターフェースを実装するのは簡単だ。EditField のサブクラスを NotifierEditField という名前で次のように定義する。
サンプル・プロジェクトをご覧いただければ上記メソッドの実装コードがおわかりいただけると思うが、以下に少しばかり解説を付す。TextChange イベント・ハンドラでは Receiver 配列を順番にめぐって、それぞれに対して新しい Text を送信している。Open メソッドの変更は少々分かりづらいが、こうすることで、プログラムの開始時に全ての TextChangedReceiver が適切な情報を取得することができるのだ。
Example2 で使われているテクニックは、常に正しいアプローチであるとは限らないものだ。これには少なくとも 2つの理由がある。第一に、TextChange イベントを通知してもらいたいオブジェクトが、何らかの理由によりサブクラス化できないとしたらどうだろう。例えば、REALbasic には、DatabaseQuery のようにサブクラス化が不可能なコントロールがある。第二に、大きなプロジェクトになると、コールバックの実装をすればするほど、サブクラスが増えていき、その管理の煩わしさに追われるハメになることだ。
にもかかわらずここで紹介するのは、バインディング・オブジェクトについて言及したいからなのだ。バインディング・オブジェクトは自分自身をあるオブジェクトに登録して、どのオブジェクトに何を通知すべきかを知らせる。ありがちなことだが、こうしたオブジェクトをサブクラス化することが必要になることもあるだろう。だが、いったん書いてしまうと、たいていはその大部分を忘れてしまうものだ。
このテクニックを用いたサンプルは、私が REALbasic のオブジェクト・バインディングに関するドキュメントを仕上げたら、公開する予定だ。
次のような状況を考えてみて欲しい。あなたのアプリケーション urlList は URL の一覧リストを表示する能力がある。そして、そのうちの一つを修正したら、リストの方も更新したい。もちろん、ListBox コントロールを DatabaseQuery コントロールとバインディングすることで、これは実現可能だ。けれども、リストサイズが大きくなるにつけ、この方法では無理が出てくる。そこで、必要な row のみを更新する方法を代わりに使う。このサンプル・プロジェクトでは、ListBox のサブクラスは UpdateClient インターフェースを実装している。UpdateClient は更新通知を受け取るために Object_Manager オブジェクトに登録される。URL の変更が Object_Manager オブジェクト(Application のプロパティとして保持される)に通知されると、Object_Manager オブジェクトは全ての client にそれをさらに通知するという仕組みだ。
このサンプルは、REALbasic の組み込みデータベースを使っており、REALDatabase の使用例にもなっている。