OSTRACISM CO.

C#とObjective-CとJavaと...

コールバック

 メインウィンドウのクラス(あるいは制御クラス)のGGG4W、GGG4M、GGG4J(以下GGG4X)にはメインウインドウに配置したGUI部品の反応を作りこむのだが、検索にからみ検索を担当しているTGraphicSearchクラスから
1. いまどのファイルを処理しているか
2. 何がヒットしたか
3. 検索が終わった
の3つを通知する方法が必要である。
 一般的にコールバックと呼ばれており、古典的Cでは関数ポインタの登録という方法で行われている。GGG4Xクラス側の関数をTGraphicSearchクラスに登録して呼んでもらうのである。
 C++ではメンバ変数のない多重継承を用意するのが正統かと思うが、GripGropではそのまま単にメインウィンドウのクラスを登録してメソッド呼び出しをしている(nfind.cppのNFind::DisplayInfo() など)。下が上の構造を知っているという意味で汎用性がなくあまり良くない。

C#

 C#にはまさしくこういう処理のためにデリゲートという仕組みがある。他のクラスにメソッドを登録できる。そのためのキーワードがdelegate。

 TGraphicSearchクラスにメソッドの型の定義。
public delegate void ProgressCallbackHandler(TFileInfo info);
 その型で変数を宣言。
public ProgressCallbackHandler ProgressCallback = null;
 GGG4Wクラス側で初期化時にメソッドを設定。
m_gs = new TGraphicSearch();
...
m_gs.ProgressCallback = new TGraphicSearch.ProgressCallbackHandler(OnProgress);
 TGraphicSearchクラス側で呼び出し。
TFileInfo current = new TFileInfo(files[i]);
if (ProgressCallback != null)
	ProgressCallback(current);
 TGraphicSearchクラスはどんなクラスが登録したProgressCallbackだろうと呼び出すことになる。登録した側がそれをプログレスに使うかどうかには関知しないという意味でクラスの独立性が確保される。

Objective-C

 Cocoa環境にはデリゲートという概念はあるが、それはOjective-C言語でサポートされているものではなくCocoaのクラスライブラリでのイベントの取り回しに関わるもので、汎用的な仕組みではない。
 Objective-Cでこういった場面で使うのがプロトコルで、キーワードは@protocol。
 プロトコルはメンバ変数を持たない仮想クラスのようなもので、多重継承できないObjective-Cでも複数のプロトコルを継承(採用)してクラスの定義に使える。プロトコルは自身の実装を持たない定義だけのもので、インスタンス化できない。必ず他のクラスにプロトコルを加えるという形になる。プロトコルはメソッドを実装せず、メンバ変数も持たないのでダイアモンド継承問題などの問題もない。

 TGraphicSearch側でプロトコルを定義。
@protocol TGraphicSearchCallback
- (void)progressCallback:(TFileInfo *)info;
@end
 そのプロトコルの変数を宣言。
@interface TGraphicSearch : NSObject {
	id<TGraphicSearchCallback> m_context;
	...
}
...
@end
 GGG4Mクラスをプロトコルに対応。
@interface GGG4M : NSObject <TGraphicSearchCallback> {
...
}
...
- (void)progressCallback:(TFileInfo *)info;
...
@end
 GGG4Mクラス側で初期化時にプロトコル(自身)を設定。
m_gs = [[TGraphicSearch alloc] initWithContext:(id)self];
 TGraphicSearchクラス側で呼び出し。
TFileInfo *current = [[TFileInfo alloc] initWithPath: path];
[m_context progressCallback: current];
 TGraphicSearchクラスはどんなクラスが登録したプロトコルだろうと呼び出すことになる。GGG4Mクラスインスタンスのselfを登録しているが、登録されるのはGGG4MクラスとしてではなくあくまでTGraphicSearchCallbackプロトコルとしてというところがミソだ。登録した側がそれをプログレスに使うかどうかには関知しないという意味でクラスの独立性が確保される。

Java

 JavaでのコールバックはObjective-Cと似た方法になる。JavaもObjective-Cと同様に多重継承ができないが、インターフェースという仕組みがあり、そのキーワードがinterface。
 インターフェースはメンバ変数を持たない仮想クラスのようなもので、多重継承できないJavaでも複数のインターフェースをクラスの定義に使える。Objective-Cのプロトコルと似ている。インターフェースは、Objective-CのプロトコルのJavaでの表現といえる。インターフェースは自身の実装を持たない定義だけのもので、インスタンス化できない。必ず他のクラスにインターフェースを加えるという形になる。
 Javaでは無名のクラスが使えるので今回はその仕組みを使い、GGG4Jクラスに継承させることなくコールバックを記述してみた。

 TGraphicSearch側でインターフェースを定義。
public class TGraphicSearch {
    public interface Callback {
        public void progressCallback(TFileInfo info);
    }
	...
}
 そのインターフェースの変数を宣言。
public class TGraphicSearch {
    ...
    public Callback m_context = null;
    ...
}
 GGG4Jクラス側で初期化時にプロトコルの無名クラスを記述、TGraphicSearchに設定。
m_gs = new TGraphicSearch();
...
m_gs.m_context = new TGraphicSearch.Callback() {
	...
    public void progressCallback(TFileInfo info) {
	  	...
    }
	...
};
 TGraphicSearchクラス側で呼び出し。
TFileInfo current = new TFileInfo(files[i].getPath());
if (m_context != null)
    m_context.progressCallback(current);
 Objective-Cと同等にクラスの独立性が確保される。

2005.07.04
2005.09.18
「インデックス」へ戻る

OSTRACISM CO.
OSTRA / Takeshi Yoneki