2-4 継承 vs コールバック


ボタンが押されたときの動作

課題3では、ボタンが押されたときの動作は決まっており、必ずプログラムを終了させていた。 しかし GUI 部品としての使い勝手を考えると、ボタンが押されたときの動作は、toolkit のユーザが簡単に変更できるようにしておくべきである。

もっとも単純な方法は、ユーザに Button クラスのサブクラスを、動作ごとに個別に定義させる方法である。

Button クラスの定義をこのように修正すれば、ユーザは mouseClick() を上書きして、自分の好きな処理をさせられる。


コールバック関数

サブクラスを定義することで、ボタンが押されたときの動作を変更させる方法は、 クラスの継承機構を使った方法であり、非常に素直な方法であるといえる。

しかしこの方法には、ふたつの重大な欠点がある。

この問題を回避するために使われる技法がコールバック関数 (callback function) と呼ばれるものである。 簡単にいうと、ボタンが押されたときの処理は独立した関数として定義し、オブジェクトにはその関数のポインタをあらかじめ渡しておく。 そしてボタンが押されたときは、その関数ポインタがさす関数を実行する、というものである。

ここで関数ポインタ callback_function は Button クラスのデータ・メンバーである。


Listener オブジェクト

コールバック関数の技法は有用であるが、残念ながら関数ポインタはオブジェクト指向言語にはあまりなじまない。 実際、Java 言語では、関数ポインタに相当する機構は存在しない。

オブジェクト指向言語では、コールバック関数の代わりに、コールバックを受けるオブジェクトを定義してこれにかえる。 Java 言語の AWT では、このようなオブジェクトを Listener と呼んでいる。

Listener オブジェクトを使うと、Button クラスは次のように変更される。

ここで listener は、Button クラスのデータ・メンバーで、型は Listener* である。

ボタンが押されたときの動作は、Listener オブジェクトのメソッド action() として定義される。 action() の中身は、動作の種類ごとに変えなければならないので、ユーザは Listener クラスのサブクラスを定義し、望みの動作を action() として定義しなければならない。 Button オブジェクトに、コールバックの受け手のオブジェクトとして登録するのは、この Listener のサブクラスのオブジェクトである。

Listener オブジェクトの action() の中身は、動作の種類ごとに変えなければならないので、ボタンが押されたときの動作に合わせて多数の Listener のサブクラスを定義しなければならないことに違いはない。

しかし多重継承あるいは Java 言語の interface を活用すれば、わざわざ Listener のサブクラスを新たに定義しなくてもすむことがある。

上のプログラムでは、既に存在する MyApplication オブジェクトに Listener オブジェクトとしての役割も負わせている。 これによって、Listener オブジェクトとして新たにオブジェクトを生成せずにすむようになる。 action() を Listener の独立したサブクラスの中で定義するのではなく、他の適当なクラス (例では MyApplication) の定義の中に、混ぜて定義するのがポイントである。 action() を定義したクラスが Listener を継承 (Java 言語では implements) していれば、そのクラスが他のクラスを継承していても、そのクラスのオブジェクトを Listener オブジェクトとして addListener() の引数に指定することができる。


Listener の利点

Listener オブジェクトを使った方法の利点は、GUI 部品のオブジェクトから、ボタンが押されたときなどの処理の記述を分離して、別のオブジェクトにできることである。

クラスの継承機構を使った方法と異なり、複数の Button オブジェクトの動作を単一の Listener オブジェクトで制御することも可能である。 またその場合、Button オブジェクトがいくつかのサブクラスに分かれていても、定義しなければならない Listener のサブクラスはひとつである。

オブジェクト指向による設計では、機能ごとにオブジェクトを分割していくことが、ときに大切である。 分割することにより、プログラムの保守性や拡張性が改善されることが多い。 また Listener の例のように、分割することによって、プログラム中にあらわれるクラスの総数をおさえることもできる。 しかしながら、無闇にオブジェクトを細分化すればよいわけではない。 その細分化によって、本当に保守性や拡張性が向上しているか、確かめながら設計をすすめることが大切である。


課題4

1回目の初めに示したように引き算をおこなうプログラムを作れ。 (メニューバーは不要。)

subtract.cc, textbox.h, textbox.cc を元に拡張して作成せよ。 これらのプログラムをコンパイルするには、

とする。

textbox.h, textbox.cc 中で定義されているクラス Textbox は、文字列を入力するためのフィールドを表示する GUI 部品のためのクラスである。 当然、Textbox は GuiObject のサブクラスである。

Textbox が複数個存在するときには、最後にマウスでクリックした方の Textbox がキー入力を受けつける。(キーボード・フォーカスがあたっているという。) Textbox の buttonDown() は上書きされており、マウスのボタンが押されると、Xwindow の setKeyboardFocus() を呼び、キー入力イベントが以後、その Textbox に渡されるように指示する。

キー入力があるたびに、計算結果を更新して表示するため、Textbox の keyDown() を修正して、Lister オブジェクトを呼ぶようにせよ。 そして subtract.cc 側で Lister オブジェクトを定義し、計算結果が更新されるようにせよ。

クラス Textbox には、イベントの処理用メソッドの他に次のようなメソッドが用意されている。

これらのメソッドを使って、キー入力時に、引く数と引かれる数の値をとりだし、答のフィールドに値を書きこむようにすればよい。 (答のフィールドとして Label でなく Textbox を使う場合。 Label を使ってもよい。)



目次へ戻る

Copyright (C) 1999-2000 Shigeru Chiba

Email: chiba@is.tsukuba.ac.jp