Msg クラスは、モジュール同士で通知するメッセージのことで、 Windows など GUI で言われるメッセージと同じ役割をします。 Command Pattern に相当します。
typedef struct {
int type; /* 型 ID */
enum {
PosMsg posMsg; /* pos タイプのメッセージ */
MouseMsg mouseMsg; /* mouse タイプのメッセージ */
: :
} t;
} App_Msg;
|
上記の PosMsg や MouseMsg は、具体的なメッセージのクラス
(ここでは
typedef struct {
int id;
int x,y;
} PosMsg;
#define PosMsg_typeID 0x8001 /* PosMsg 型の ID */
|
メッセージ共用体の実体(変数)は、コンテキスト(グローバル)の
変数にするのではなく、アプリケーションの上位の関数の
ローカル変数にします。なぜなら、メッセージは
恒久的に存在するものではなく、一時的に
モジュール間の通知のためだけに使われるためです。
メッセージ共用体を初期化するには、派生クラスの他に type メンバ変数を初期化する必要があります。
{
App_Msg* msg = malloc( sizeof(App_Msg) );
PosMsg_init( &msg->t.posMsg );
msg->type = PosMsg_typeID;
}
|
void MessageStation( AppMsg* msg )
{
switch ( msg->type ) {
case PosMsg_typeID: {
PosMsg* m = &msg->t.posMsg;
pos( m->x, m->y );
}
case MouseMsg_typeID: {
MouseMsg* m = &msg->t.mouseMsg;
pos( m->x, m->y );
}
}
}
|
メッセージは、抽象的な見方(抽象クラス)と具体的な見方(派生クラス)の 両方ができます。 メッセージを中継するだけの関数にとっては、メッセージの 具体的な内容は気にする必要はありません。 たとえば、ある変数のアドレスを void* 型にキャストしてしまえば、 その具体的な変数の内容(構造体の構造)を知らなくても、 特定の関数やオブジェクトにデータを転送することができます。
となると、抽象的なメッセージは、void* 型ということになりますが、
今度は、メッセージを利用する(解読する)ときに void* 型では
不充分な場合があります。なぜなら、そのメッセージの実体の型
(具体的な型)がわからないことがあるからです。
メッセージがどの型なのかを知るためには、その情報をメッセージ自体に
付ければいいですから、Msg クラスに type メンバ変数を入れています。
Msg クラスは、メッセージを抽象化できるので、 1つの通り道(たとえば、関数の引数)にあらゆる メッセージを通すことができるようになります。 Msg クラスを使用することにより、関数の引数をすべて Msg クラスにすることは可能ですが現実的ではありません。 その理由は、次の通りです。
メッセージを抽象化することが生かされる場面は、
ある関数の出力(関数の呼び出し方向と逆向きのデータ)が
多くの型のうちのどれかがになっているときです。
逆に、ある関数の入力(関数の呼び出し方向と同じ向きのデータ)が
多くの型になっている場合は、Msg クラスをその関数の入力にしないで、
それぞれの派生クラスごとに関数(分岐)を作成し、
ダウンキャストしてから呼び出すようにした方がいいでしょう。
なぜなら、上位の関数ほど多くの種類のメッセージを扱いますが、
下位の関数ほど特定のメッセージを扱うようになるためです。