Java による実装

Java による実装


Java による実装例だ。

いろんな実装が考えられるけど、ここでは Method オブジェクトを使ってみた。この場合、リクエストはメソッドの名前として与えられるんだ。それぞれのオブジェクトで、対応するメソッドが実装されているかどうかを調べ、ハンドラを呼び出すか、チェインをたぐって次のオブジェクトに投げるかを決定する。



Handler クラス

/* チェインのための Handler クラス */
public class Handler {
 /* 次の handler */
 Handler _successor;

 /* コンストラクタ */
 public Handler(Handler successor) {
  _successor = successor;
 }

 /* リクエスを投げる */
 public void sendRequest(String request) {
  try {
   /* リクエストの名前から Method を取得 */
   Method method =
    this.getClass().getMethod(request, null);
   /* ハンドラの呼び出し */
   method.invoke(this, null);
  }
  catch(NoSuchMethodException e) {
  if (_successor != null) {
   /* チェインをたぐって、 */
   /* 次のオブジェクトにリクエストを投げる */
   _successor.sendRequest(request);
  }
 }
 catch(Exception e) {
   /* その他の例外 */
  }
 }
}

Button クラス

/* Button クラス */
public class Button extends Handler {
 /* コンストラクタ */
 public Button(Handler handler) {
  super(handler);
 }

 /* help リクエストのためのハンドラ */
 public void help() {
  /* help リクエストの処理 */
 }
}

Dialog クラス

/* Dialog クラス */
public class Dialog extends Handler {
 /* コンストラクタ */
 public Dialog(Handler handler) {
  super(handler);
 }

 /* print リクエストのためのハンドラ */
 public void print() {
  /* print リクエストの処理 */
 }
}

Application クラス

/* Application クラス */
public class Application extends Handler {
 /* コンストラクタ */
 public Application(Handler handler) {
  super(handler);
 }

 /* preview リクエストのためのハンドラ */
 public void preview() {
  /* preview リクエストの処理 */
 }
}

main()

public class Test {
 public static void main(String args[]) {
  /* それぞれのインスタンスを作る */
  Application app = new Application(null);
  Dialog dialog = new Dialog(app);
  Button button = new Button(dialog);

  /* help リクエストを投げる */
  button.sendRequest("help");
  /* print リクエストを投げる */
  button.sendRequest("print");
  /* preview リクエストを投げる */
  button.sendRequest("preview");
 }
}

これでオッケー!Method オブジェクトを使った場合、次のような利点がある。

・Handler クラスは、リクエストの種類を知る必要がない。リクエストの種類が増えても、Handler クラスを変更する必要がない。

・オブジェクトがリクエストに対するハンドラを実装しているかどうかを、メソッドの実装を調べることで実現できる。

・メッセージの種類が増えても、すべてのオブジェクトを変更する必要がない。ハンドラを実装するオブジェクトのみ変更すればよい。

この利点は、ほかのメッセージ送信を用いている言語のものと同じだね。かわりに欠点としては、C++ のように switch を用いたときに比べると、パフォーマンスが悪くなりがち、というとこか。

メンバ関数ポインタをリクエストに使う


ということで、次はメンバ関数ポインタを使ってみた例だ。リクエストとしてメンバ関数をポインタを使う。こんな風に。


リクエスト定数

/* リクエストを表す定数 */
const Handler::request_id
 HELP_REQUEST = &Handler::help;
const Handler::request_id
 PRINT_REQUEST = &Handler::print;
const Handler::request_id
 PREVIEW_REQUEST = &Handler::preview;

Handler クラス

/* チェインのための Handler クラス */
class Handler
{
public:
 typedef void (Handler::*request_id)();

private:
 /* 次の handler */
 Handler* _successor;

public:
 /* コンストラクタ */
 Handler(Handler* successor) :
  _successor(successor) {}

 /* リクエストを処理する */
 void handleRequest(request_id request);

 /* リクエストのハンドラ */
 virtual void help();
 virtual void print();
 virtual void preview();
};


このように、Handler クラスのハンドラのメンバ関数ポインタを、リクエストとして使うんだ。これを使うと、リクエストとハンドラを結びつける handlerRequest() は、次のようになる。ただしこの方法だと、それぞれのデフォルトハンドラを実装する必要が出てきてしまうんだ。その実装の中でチェインをたぐる動作をすることになる。たとえば、デフォルトの help ハンドラは次のような感じ。



Handler::handleRequest()

/* リクエストを処理する */
void Handler::handleRequest(request_id request)
{
 (this->*request)();
}

Handler::help()

/* デフォルトの help ハンドラ */
void Handler::help()
{
 if (_successor) {
  _successor->handleRequest(HELP_REQUEST);
 }
}

完全なソースコードは、ファイルを参照してくれ。この実装だと、さっきの問題点のうち「リクエストとハンドラの対応付けを、コードの中でやらなくてはいけない」という点は解消される。ただし、

・リクエストを動的に追加できない。

という、新たな問題点が生じることになってしまう。さっきはリクエストを数値で与えたけど、今度はメンバ関数ポインタだからね。



メンバ関数テンプレートを使う


じゃあ、これはどうよ、ってんで出てきたのが、次の実装。これは、動的にリクエストとハンドラを増やすことと、同じ機能を実現することを目的に書かれたものだ。どうするかっていうと、handleRequest をあらゆるリクエストを受け付ける、メンバ関数テンプレートとして定義する。そして、渡されたタイプによって、呼び分ける handlerRequestHelper() っていう関数を作るんだ。この中で、ハンドラを検索する。

Handler::handleRquest(), Handler::handerRequestHelper()

/* リクエストを処理する */
template<typename T>
void Handler::handleRequest(T request)
{
  handleRequestHelper(
   request,
   int_to_type<is_same_type<request_id, T>::value>());
}

///request の型が void (Handler::*)() の場合
template<typename T>
void Handler::handleRequestHelper(T request, int_to_type<true>)
{
  (this->*request)();
}

///request の型が void (Handler::*)() 以外の場合
template<typename T>

void Handler::handleRequestHelper(T request, int_to_type<false>)
{
  /*
   注意). T が引数無しのメンバ関数ポインタ型( R (Obj::*)() )
   以外ならコンパイル不可
  */
  typename type_traits<T>::object_type* obj =
  dynamic_cast<typename type_traits<T>::object_type*>(this);
  if(obj)
    (obj->*request)();  
  else
    defaultHandler<T, const char*>(request, "Unknown");
}

あらたにハンドラを登録するには、次のマクロを使う。


リクエスト特性定義用マクロ

/* リクエスト特性定義用マクロ */
#define REGISTER_REQUEST_TRAITS(RESULT, HANDLER, REQUEST) \
template<> \
class Handler::RequestTraits<RESULT (HANDLER::*)(), &HANDLER::REQUEST> \
{   \
public :  \
  typedef RESULT (HANDLER::*request_type)();   \
  static const request_type identifier;   \
  static const char name[];    \
};      \
const Handler::RequestTraits<RESULT (HANDLER::*)(),   \
  &HANDLER::REQUEST>::request_type      \
Handler::RequestTraits<RESULT (HANDLER::*)(),     \
  &HANDLER::REQUEST>::identifier = &HANDLER::REQUEST;   \
const char     \
Handler::RequestTraits<RESULT (HANDLER::*)(),     \
  &HANDLER::REQUEST>::name[] = "" #REQUEST;


詳細は実装のファイルを見てくれ。正直な話、「よくやったな~」という気持ちと、「ここまでやるか?」という気持ちが半々だ。こうなると、これが C++ の言語特性を生かした実装か、と言われてもちょっと微妙な感じも。

いろいろ見てきたけど、生粋の C++ 使いの人には不満を感じるところもあるかもしれない。「問題、問題っていうけど、そもそも動的なリクエストのハンドラの追加って意味あるの?」とか。まぁ、ちょっと待って。ここでの目的は、Chain of Responsibility の実装の議論ではなく、これを題材にした各言語の比較なので。他の言語での実装と見比べて、C++ の特徴を感じてください。