Entry

プログラミングメモ - Factory パターンをそれっぽく作ってみる

2008年09月05日

先日クラス図だけ紹介したんですけど,もう少し詰めて実装してみました。とりあえず,クライアント側から使い方を見てみましょう。ちなみに,以下に紹介するプログラムを実行すると,"This is LoginAction exec!"と表示されます。

#include <iostream>
#include "ActionFactory.h"
#include "Action.h"

using namespace qb;

int
main(int argc, char* argv[]) {
    ActionFactory* af = ActionFactory::getInstance();
    // 登録する
    af->setCreator("login", &LoginAction::createAction);
    // オブジェクトを作る
    Action* action = af->create("login");
    // 実行する
    if (action) {
        ((LoginAction*)action)->exec();
        delete action;
    }
    else {
        std::cout << "Error!" << std::endl;
    }

    delete af;

    return 0;
}

注目してもらいたいのは,下の部分。

    // オブジェクトを作る
    Action* action = af->create("login");

"login" という文字列を指定して LoginAction クラスのインスタンスを作っています。Action クラスのポインタで受けているけれど,これは LoginAction クラスの基底クラスです。本当だったら,クラス名を指定して,new LoginAction() とかやらなきゃいけないわけですけど,ここでは任意の文字列でもって任意のインスタンスを作れるというわけです。リフレクションみたいですねー。

これでユーザが気まぐれで入力した引数なんかを元にして,動的に任意のオブジェクトを作ることができます。

実行については,クラス名でキャストしなくちゃいけないんですけど,インスタンスができたら,それの任意のメソッドを起動するのはあまり難しくありません。

ということで,どんな風になっているのか。まず,クラスがどのように定義されているかを見てみましょう。

まとめて宣言していますけど,Action というクラスと,その派生クラスである LoginAction クラスを宣言しています。今回は,フレームワークを作っている一環なので,LoginAction クラスは,フレームワークライブラリの利用者が Action を継承して利用することを考えています。

#ifndef ACTION_H_INCLUDED
#define ACTION_H_INCLUDED

namespace qb {
    class Action {
    public:
        virtual ~Action();
    protected:
        Action();
    public:
    };

    // sample Action
    class LoginAction : public Action {
    public:
        virtual ~LoginAction();
    public:
        virtual void exec();
        static Action* createAction();
    protected:
        LoginAction();
    };
}

#endif  // ACTION_H_INCLUDED

定義も載せます。

#include <iostream>
#include "Action.h"
#include "LoginAction.h"

qb::Action::Action() {
}
qb::Action::~Action() {
}

// Sample Action
qb::LoginAction::LoginAction() {
}

qb::LoginAction::~LoginAction() {
}

void
qb::LoginAction::exec() {
    std::cout << "This is LoginAction exec!" << std::endl;
    return;
}

qb::Action*
qb::LoginAction::createAction() {
    return new LoginAction();
}

ポイントは,LoginAction クラスの静的メンバである createAction() メソッド。自分自身を作るメソッドを定義しています。上記の main() では,このメソッドと,任意の文字列を ActionFactory という Factory クラスに登録(setCreator())しています。

じゃあ,ActionFactory クラスはどうなっているのか。

#ifndef ACTION_FACTORY_H_INCLUDED
#define ACTION_FACTORY_H_INCLUDED

#include <string>
#include <map>
#include "Action.h"

namespace qb {
    class ActionFactory {
    public:
        typedef Action* (*Creator)();
    public:
        virtual ~ActionFactory();
    private:
        ActionFactory();
    public:
        static ActionFactory* getInstance();
        bool setCreator(const std::string& id, const Creator creator);
        Action* create(const std::string& id);
    private:
        Creator getCreator(const std::string& id);
    private:
        static ActionFactory* me_;
        std::map<std::string, Creator> creators_;
    };
}

#endif  // ACTION_FACTORY_H_INCLUDED

一応,あちこちに複数インスタンス化されると困るので,Singleton なクラスにしています。インスタンス化するときは,静的メンバである getInstance() を呼びます。それよりも重要なのは,この部分。

    public:
        typedef Action* (*Creator)();

Action クラスを作るメソッドについて,関数ポインタを typedef しています。で,これを creators_ という std::map コンテナに格納するというわけです。もう仕組みは想像できたでしょうか。最後に ActionFactory クラスの定義を紹介します。

#include <iostream>
#include <string>
#include <map>
#include "Action.h"
#include "ActionFactory.h"

qb::ActionFactory* qb::ActionFactory::me_ = 0;

qb::ActionFactory::ActionFactory() {
}

qb::ActionFactory::~ActionFactory() {
}

qb::ActionFactory*
qb::ActionFactory::getInstance() {
    if (me_ == 0) {
        try {
            me_ = new ActionFactory();
        }
        catch (std::bad_alloc) {
            // [todo] throw exception
        }
    }
    return me_;
}

bool
qb::ActionFactory::setCreator(const std::string& id, const Creator creator) {
    bool ret;
    std::map<std::string, Creator>::iterator itr;
    if (getCreator(id) != 0) {
        ret = false;
    }
    else {
        std::pair<std::string, Creator> p(id, creator);
        creators_.insert(p);
        ret = true;
    }
    return ret;
}

qb::ActionFactory::Creator
qb::ActionFactory::getCreator(const std::string& id) {
    std::map<std::string, Creator>::iterator itr
        = creators_.find(id);

    Creator creator;
    if (itr != creators_.end()) {
        creator = itr->second;
    }
    else {
        creator = 0;
    }
    return creator;
}

qb::Action*
qb::ActionFactory::create(const std::string& id) {
    Creator creator = getCreator(id);
    Action* action;
    if (creator == 0) {
        action = 0;
    }
    else {
        action = (*creator)();
    }
    return action;
}

こんな感じ。main() で呼んでいる setCreator では,std::map コンテナに,引数の文字列と関数ポインタを登録します。実際にインスタンス化するときは,以下のように,登録しておいた関数ポインタからインスタンス化するわけです。Factory パターンにありがちな,長大な switch-文がないことにも注目です。

qb::Action*
qb::ActionFactory::create(const std::string& id) {
    Creator creator = getCreator(id);
    Action* action;
    if (creator == 0) {
        action = 0;
    }
    else {
        action = (*creator)();
    }
    return action;
}

実はこのやり方,あたしゃこちらを参考にさせてもらったんですけど,この前から読んでいる『Modern C++ Design』にも,もう少し洗練された方法が載っていたのでした。

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

『Modern C++ Design』のやり方は洗練されすぎていて,テンプレートとかをガリガリ使っているんですけど,ここではそこまでする必要はないので,おおむねこの方針でいいかなぁ……と。むやみにポリモーフィックにしても,型安全性が損なわれちゃいますしね。

あとは,実行時にキャストするところをどうにかしたいところ。先日作ったクラス図では,実行用のメソッドを登録するような std::map コンテナを考えていたんですけど,あまり上手い手だとは思えません。Functor にでもしたらうまくいくかしらん。

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