メッセージ [Msg]

Msg クラスは、モジュール同士で通知するメッセージのことで、 Windows など GUI で言われるメッセージと同じ役割をします。 Command Pattern に相当します。


1. ◆ メッセージ共用体の構造

メッセージ共用体は、メッセージを抽象化したクラスです。 構造は次のようになっています。
typedef struct {
  int  type;         /* 型 ID */
  enum {
    PosMsg    posMsg;     /* pos タイプのメッセージ */
    MouseMsg  mouseMsg;   /* mouse タイプのメッセージ */
       :         :
  } t;
} App_Msg;
type と t メンバ変数はこの名前に統一します。 各タイプのメッセージのメンバ変数は、 クラス名に接頭辞を小文字にしたものに統一します。

上記の PosMsg や MouseMsg は、具体的なメッセージのクラス (ここでは派生クラスと呼ぶ)です。
typedef struct {
  int  id;
  int  x,y;
} PosMsg;

#define  PosMsg_typeID    0x8001    /* PosMsg 型の ID */
派生クラスの1つに対して1つの上記のような #define を定義してください。 ID の名前は、クラス名に _typeID を付けたものに統一します。 ただし、それぞれの派生クラスの型 ID の値は、 同じ値に重ならないようにしてください。

メッセージ共用体の実体(変数)は、コンテキスト(グローバル)の 変数にするのではなく、アプリケーションの上位の関数の ローカル変数にします。なぜなら、メッセージは 恒久的に存在するものではなく、一時的に モジュール間の通知のためだけに使われるためです。


2. ◆ メッセージ共用体の初期化の仕方

メッセージ共用体を初期化するには、派生クラスの他に type メンバ変数を初期化する必要があります。
{
  App_Msg*  msg = malloc( sizeof(App_Msg) );

  PosMsg_init( &msg->t.posMsg );
  msg->type = PosMsg_typeID;
}


3. ◆ 派生クラスへのダウン・キャストの仕方

ダウンキャストするには、メッセージの型によって switch 文で分岐して、 派生クラスへのポインタに共用体のアドレスを格納します。
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 メンバ変数を入れています。


4. ◆ 用途、使わなくてもいいケース

Msg クラスは、メッセージを抽象化できるので、 1つの通り道(たとえば、関数の引数)にあらゆる メッセージを通すことができるようになります。 Msg クラスを使用することにより、関数の引数をすべて Msg クラスにすることは可能ですが現実的ではありません。 その理由は、次の通りです。

メッセージを抽象化することが生かされる場面は、 ある関数の出力(関数の呼び出し方向と逆向きのデータ)が 多くの型のうちのどれかがになっているときです。
逆に、ある関数の入力(関数の呼び出し方向と同じ向きのデータ)が 多くの型になっている場合は、Msg クラスをその関数の入力にしないで、 それぞれの派生クラスごとに関数(分岐)を作成し、 ダウンキャストしてから呼び出すようにした方がいいでしょう。 なぜなら、上位の関数ほど多くの種類のメッセージを扱いますが、 下位の関数ほど特定のメッセージを扱うようになるためです。


written by Masanori Toda from May.21.1999