Entry

プログラミングメモ - Action クラス周りとか作ってみた

2009年03月17日

C++ で Web コンテナ(というか CGI のフレームワーク)を,相変わらずゴニョゴニョ。Struts に言う Action クラスをどうやって作ろうか悩んでたんですけど,とりあえず作ってみました。

Action クラスの問題は,文字列でやってくる HTTP リクエストから,どうやって適切なオブジェクトを生成するかなんですけれど,これが結構面倒っちい。この前はハッシュでどうとかとか言ってたんですけど(参照:qune: プログラミングメモ - C++ で Web コンテナの妄想ふたたび),ハッシュを乗せると,衝突したときの手当ての分だけプログラムが大きくなってしまいそうなのでボツ。代わりに,素直に文字列をキーにしてオブジェクトを生成するようにしました。

参考にしたのは,こちらの本。やっぱり強力だぜ。

Modern C++ Design―ジェネリック・プログラミングおよびデザイン・パターンを利用するための究極のテンプレート活用術 (C++ In‐Depth Series)
Modern C++ Design
posted with amazlet at 09.03.17
アンドレイ アレキサンドレスク
ピアソンエデュケーション
売り上げランキング: 37216
おすすめ度の平均: 5.0
5 必読
5 文句なしに良書です。しかし内容は難解です
5 テンプレートの威力
5 テンプレート
4 凄いのは分かった。でも、僕には使えません(泣

本書では,C++ で動的に任意のオブジェクトを生成する方法を紹介しています。ここではダウンキャストに苦労していて,type_info を使う方法なんかを紹介しているけれども,今回は Strategy パターンの亜種で HTTP リクエストからオブジェクトを作るので,ダウンキャストする必要はありません(Action::execute() だけ実装されていればいい)。また,クラスを識別する文字列についても,Action クラスの場合,キーは QUERY_STRING から作る URI になるので,ユーザ(フレームワークを使う人)の設定項目として丸投げしちゃうことができます。

文字列をキーにする場合,検索/登録するコストが高くなりそうで嫌だったんですけれど,仕方がない。もっとも,STL を使ったので,実装自体はそんなに苦労はしませんでした。

つことで,こんな風に作ってみた。ちと長いけど,説明してみます。まず,どうやって使うのか,使用側から見えるところをば(ライブラリ本体ではありません)。

#include "../action.h"

int
main(int argc, char* argv[]) {
    MyApp app;
    // todo: move to App::init() method.
    app.setActionMap();
    // get action object from string
    Action& action = ActionFactory::instance().create("derived");
    // execute
    action.execute();
    // action objects created by ActionFactory::create() are deleted by
    // ActionFactory class. DO NOT DELETE THEM.
    return 0;
}

まず,main() 内で App クラスを継承した MyApp のオブジェクトを作ります。MyApp は,このアプリケーションの親玉で,アプリケーション全体をつかさどるインスタンスになります。MyApp には,アプリケーションの挙動を設定する項目を用意して,ユーザが自分で定義できます(まだ作ってないけど)。アプリケーションで使う Action クラスも,ここで設定する。ここでは,main() メソッド内で app オブジェクトの設定をしているけれども,コレは単に設定するためのメソッドを作ってないだけで,本当は App クラスに処理を委譲して,app オブジェクト内部で設定することになります。

ここで,アプリケーションで使う Action クラスを設定しているのが,App::setActionMap() メソッドです。Struts では,ActionMapping クラスが XML(struts-config.xml)を読む箇所に相当するんだと思う(うろ覚え)。このメソッドで XML を読んでもいいんですけど,IOの時間がもったいないので事前に定義してもらって,静的に置くことにしました(今のところ)。

で,目的の Action オブジェクトはどうやって取り出すのかというと,ActionFactory::create() メソッドに所望の文字列を渡して作ります。ここでは,"derived"という文字列から Action オブジェクトを作っています。この文字列が,QUERY_STRING に相当します。あとは適当に使う(execute() する)だけ。もらった Action オブジェクトのメモリは ActionFactory 内で管理しているので,クライアント側で delete する必要はありません。わざわざ参照で返してるので,無理矢理 delete したやつはもう知らん,というスタンスです。

さて,使い方が分かったところで(?),中身に話を移します。まずは,Action クラスの定義を見てみましょう。Action クラスは実体化できない抽象クラスで,実体を持つ Action クラスはこいつを継承して作ることになります。ここでは,テストサンプルとして,文字列"derived"から作られる DerivedAction というクラスを定義することにします。

まず,親である Action クラスの宣言。

class Action {
protected:
    Action();
public:
    virtual ~Action();
public:
    typedef Action& (*CreateHandle)();
private:
    Action(const Action& other);
    Action& operator=(const Action& rhs);
public:
    virtual bool execute() = 0;
};

ほとんど何もしません。ひとつポイントなのが,CreateHandle という名前で関数ポインタを typedef しているところです。これは,Action オブジェクトを生成する static メソッドを表しています。ここで,Action クラス自体は,この型に相当するメソッドを持っていないんですけれど,継承するクラスでは,この型に相当するメソッドを実装する必要があります。インターフェイスなので,本当は純粋仮想関数にしたかったんですけど,static メソッドを virtual で宣言するのって面倒なんですよね(というかあたしが分かってない)。

で,その子供である DerivedAction クラスの宣言。これはユーザが宣言/定義する。

class DerivedAction : public Action {
public:
    DerivedAction();
    virtual ~DerivedAction();
private:
    DerivedAction(const DerivedAction& other);
    DerivedAction& operator=(const DerivedAction& rhs);
public:
    static Action& create();
public:
    virtual bool execute();
}

static な create() メソッドと execute() インスタンスメソッドを実装します。実装は簡単。

Action&
DerivedAction::create() {
    Action* action = new DerivedAction();
    return *action;
}

bool
DerivedAction::execute() {
    std::cout << "hello" << std::endl;
    return true;
}

static な create() メソッドは,ただ new して返すだけです。std::bad_alloc の例外処理がないけれど,これは落ち着いてからまた後で。実は,このメソッドが今回のミソなんですけれど,これは ActionFactory の中で説明します。

一方,DerivedAction クラスの本体は,execute() メソッドです。ここでは,ただ"hello"と表示するだけ。この execute() メソッドの中身をユーザが作ることになります。DB にアクセスするもよし,大規模な行列演算をするもよし。MVC にいう Model と接続する部分です。

これで,目的の Action クラスを宣言することができました。続きまして,このクラスのオブジェクトを文字列から生成する ActionFactory クラスの宣言を見てみましょう。

class ActionFactory {
private:
    ActionFactory();
    ActionFactory(const ActionFactory& other);
    ActionFactory& operator=(const ActionFactory& rhs);
public:
    virtual ~ActionFactory();
public:
    typedef std::map<std::string, Action::CreateHandle>::const_iterator MapItr;
    typedef std::pair<std::string, Action::CreateHandle> MapPair;
public:
    Action& create(const std::string& path);
    bool setActionMap(const std::string& path, const Action::CreateHandle hdr);
public:
    static ActionFactory& instance();
private:
    void pushAction(Action* action);
private:
    static ActionFactory* me_;
    std::vector<Action*> createdActions_;
    std::map<std::string, Action::CreateHandle> actionMap_;
};

Factory パターンを使ってるので,基本的に Singleton で作ります。いくつもあったら Action クラスを一元的に管理できませんしね。そのせいでちと読みづらいんですけれど,このクラスは,std::map<std::string, Action::CreateHandle> actionMap_ を中心にして作られています。この変数に,文字列(std::map<std::string)と Action クラスを実体化するメソッドへのハンドラ(Action::CreateHandle)の組を登録しておくわけです。一方,me_ は自分自身。Singleton ですから,自分自身をとっておきます。また,createdActions_ なるメンバは,自分が作った Action クラスへのポインタを登録しておくための vector です。自分(me_)が破棄される時に,デストラクタで delete するのに使います。

うだうだ言ってないで,実装を見てみましょう。

ActionFactory* ActionFactory::me_ = 0;

ActionFactory::ActionFactory() {
}

ActionFactory::~ActionFactory() {
    std::vector<Action*>::const_iterator itr;
    for (itr = createdActions_.begin(); itr != createdActions_.end(); itr++) {
        delete *itr;
    }
}

bool
ActionFactory::setActionMap(const std::string& path,
                            const Action::CreateHandle hdr) {
    bool ret = false;
    MapItr itr = actionMap_.find(path);
    if (itr == actionMap_.end()) {
        actionMap_.insert(MapPair(path, hdr));
        ret = true;
    }
    return ret;
}

ActionFactory&
ActionFactory::instance() {
    if (me_ == 0) {
        static ActionFactory instance;
        me_ = &instance;
    }
    return *me_;
}

Action&
ActionFactory::create(const std::string& path) {
    Action* ret;
    MapItr itr = actionMap_.find(path);
    if (itr != actionMap_.end()) {
        ret = &((*itr).second());
        pushAction(ret);
    }
    return *ret;
}

void
ActionFactory::pushAction(Action* action) {
    if (action != 0) {
        createdActions_.push_back(action);
    }
}

まず,クラスが宣言された時点では,自分自身(ActionFactory オブジェクト)は存在しないので,me_ に NULL(0)を設定しておきます。

じゃあ,どの時点で ActionFactory オブジェクトができるのかというと,ActionFactory::instance() メソッドがはじめて呼ばれるときです。ここら辺は Singleton パターンの定石なので,特に説明することもありません。本来は new してヒープ上に置きたいところなんですけど,delete するタイミングがないので,同メソッド内で static 定義しています(static の意味が静的関数/変数のそれと異なることに注意)。

一方,肝心の文字列と Action オブジェクトを生成するメソッドの組を作るメソッドは,ActionFactory::setActionMap() メソッドです。普通に std::map に登録する。問題はこいつをどうやって呼ぶかなんですけれど,ここでは,冒頭の App オブジェクトから設定してもらうことにしました。前掲書籍では,グローバル変数に代入する方法でスタートアップ時に初期化してたけれども,なるべくクラスでラップしておきたいので,App クラス内で設定ということに……。このせいで,Action クラスを生成する仕組みと App クラスの結合度が高くなっちゃうんですけれど,今のところは仕方ないといったところ。

で,最後。文字列から目的のオブジェクトを生成する時は,ActionFactory::create() メソッドを使います。引数で受け取った文字列に相当する Action クラスの生成ハンドラ(Action::CreateHandle)を actionMap_ メンバから探して,このハンドラで Action オブジェクトを生成します。ここも見つからなかったときや std::bad_alloc が投げられた時の処理が入ってないけど,これも落ち着いたら入れる(忘れそうで危険だなー)。生成した Action オブジェクトのポインタは,createdActions_ メンバに push しておきます。ここに登録された領域は ActionFactory オブジェクト(me_)のデストラクタで delete されます。

……と,長くなってしまったけれど,こんな感じ。main() を実行すると,DerivedAction オブジェクトが生成されて,これの execute() メソッドを実行する。要するに"hello"とだけ表示される……と。頑張った割には地味な結果。

最後に,冒頭の main() 関数で呼んでいた MyApp::setActionMap() の実装をあげておきます。

bool
MyApp::setActionMap() const {
    bool ret;
    ActionFactory& af = ActionFactory::instance();
    ret = af.setActionMap("derived", DerivedAction::create);
    return ret;
}

今回は,割と凝集度を高めに作れた感じで自己満足。後の難題は,View へのディスパッチだなぁ……。XSLT を巻き込むつもりなんですけど,さて……どう作ろうか。

Trackback
Trackback URL:
Ads
About
Search This Site
Ads
Categories
Recent Entries
Log Archive
Syndicate This Site
Info.
クリエイティブ・コモンズ・ライセンス
Movable Type 3.36
Valid XHTML 1.1!
Valid CSS!
ブログタイムズ

© 2003-2012 AIAN