拡張式 Factory Method
ナッツ食べてます。そこそこおいしいです。
前回、FactoryMethod
を作りましたが、今回は、それを改造します。
pickles-ochazuke.hatenablog.com
基本的な FactoryMethod だと、新しいクラスを作ったときに毎度 FactoryMethod 側も新しいクラス用の処理を追加しないといけません。そこで、新しいクラスへの対応を実行中に行えるようにします。
概要
大まかに書くと、抽象基本クラスを継承した新しいクラス側に、"自身のオブジェクトを作成し、それを返す" コールバック関数を作ります。そのコールバック関数と key
となる文字列を FactoryMethod に渡し、FactoryMethod 側は、その関係を覚えておきます。実際にオブジェクトが必要になったときは、key
となる文字列を渡すことで、それに対応したコールバック関数が FactoryMethod 内で呼ばれ、作成されたオブジェクトが返ってくる。という感じです。
図にすると以下のような感じでしょうか。
上記は、Callback の登録のみしか書いていませんが、削除もできたほうがいいので、実装時には削除用の関数も作ります。
FactoryClass
まずシーケンス図にあった FactoryClass
のヘッダは以下のとおりです。
クラス名は、前回の続きなので SlimeFactory
となっています。
また、SlimeFactory で作られるオブジェクトは、すべて抽象基本クラスの ISlime
を継承しています。
#pragma once #include <string> #include <map> #include <functional> #include "ISlime.h" class SlimeFactory { public: using DoMakeCallback = std::function<ISlime*()>; SlimeFactory() = delete; ~SlimeFactory() = delete; static void RegisterSlime(const std::string& type, DoMakeCallback cb); static void UnregisterSlime(const std::string& type); static ISlime* DoMakeSlime(const std::string& type); private: using CallbackMap = std::map<std::string, DoMakeCallback>; static CallbackMap DoMakes_; };
上から順番に見ていきます。まず、using DoMakeCallback = std::function<ISlime*()>
ですが、
これは、コールバック関数の型を定義しています。using DoMakeCallback
は書くのを楽にしているだけで、std::function<ISlime*()>
が大事です。これが、登録するコールバック関数の型になります。登録したいクラスは、コールバック関数を作るとき、この型(ISlime*()
)を守る必要があります。std::function
は、関数ポインタ用のクラスだと思っておけば問題ないです。
コンストラクタとデストラクタは、今回必要ないので delete
しています。実際に FactoryClass を作るときは、1つしか作られないようにシングルトン
にするといいでしょう。
static void RegisterSlime(const std::string& type, DoMakeCallback cb)
は、コールバック関数を登録するための関数です。登録したいコールバック関数があるとき、この関数を呼び出して、引数に key
となる文字列とコールバック関数
を渡してやります。
static void UnregisterSlime(const std::string& type)
は、先ほどとは逆で、登録済みのコールバック関数を削除する関数です。使うときは、登録したときに渡した文字列と同じ文字列を引数に渡すだけです。
static ISlime* DoMakeSlime(const std::string& type)
は、オブジェクトが必要になったときに呼びます。引数に登録時の文字列を渡してやることで、生成されたオブジェクトが返ってきます。
using CallbackMap = std::map<std::string, DoMakeCallback>
は、これも型を定義しているだけです。std::map<std::string, DoMakeCallback>
は、コールバック関数を登録するための型になります。std::string
が key
となり、DoMakeCallback
が登録されたコールバック関数
です。
最後に、static CallbackMap DoMakes_
ですが、これは、コールバック関数が登録される場所になります。
次に、ソースファイルを見ていきます。
#include "HealSlime.h" #include <iostream> // インスタンス化 SlimeFactory::CallbackMap SlimeFactory::DoMakes_; void SlimeFactory::RegisterSlime(const std::string& type, DoMakeCallback cb) { DoMakes_[type] = cb; } void SlimeFactory::UnregisterSlime(const std::string& type) { DoMakes_.erase(type); } ISlime* SlimeFactory::DoMakeSlime(const std::string& type) { if (type == "Slime") { return new Slime(); } else if (type == "SheSlime") { return new SheSlime(); } else if (type == "HealSlime") { return new HealSlime(); } CallbackMap::iterator it = DoMakes_.find(type); if (it != DoMakes_.end()) { if (!it->second) { std::cout << "呼び出せない" << std::endl; return nullptr; } return (it->second)(); } return nullptr; }
上から順番に見ていきましょう。
まずは、SlimeFactory::CallbackMap SlimeFactory::DoMakes_
で static の変数をインスタンス化してやります。
RegisterSlime(const std::string& type, DoMakeCallback cb)
は、登録なので、登録用の変数 DoMakes_
にコールバック関数を登録しています。
UnregisterSlime(const std::string& type)
は、削除なので、type
を元に DoMakes_
に登録されているコールバック関数を削除しています。
DoMakeSlime(const std::string& type)
は、type
を元に作成すべきオブジェクトを決め、作成し、そのオブジェクトを返します。
Slime
, SheSlime
, HealSlime
は、前回作成した内容です。そこを抜けると type
と関連するコールバック関数があるか探します(CallbackMap::iterator it = DoMakes_.find(type);
)。見つかれば、そのコールバック関数が呼べるか確認(if (it != DoMakes_.end())
)し、問題なければ、呼び出して、返ってきたオブジェクトをそのまま返します(return (it->second)()
)。
以上が拡張式の FactoryMethod になります。
登録するためのコールバック関数の中身は、以下のように書きます。
static ISlime * DoMakeMyself() { return new MySlime(); }
このメソッドを RegisterSlime
の第二引数に渡してやれば、DoMakes_
に登録されます。
実行
実際に使うときは、以下のように使います。
std::function<ISlime*()> func = MySlime::DoMakeMyself; SlimeFactory::RegisterSlime("MySlime", func); ISlime* slime = SlimeFactory::DoMakeSlime("MySlime");
結果は、以下です。
ぼくが考えた最強のスライム が現れた! 続行するには何かキーを押してください . . .
以上のように、新しいクラスを作ったときは、FactoryClass 側で何も追加する必要なく、対応することが出来ました。
おわり
実際に作ってみると、することは難しそうだけど、作ってみたら簡単だった。ように感じました。コールバック関数の登録も削除も一行で済み、呼び出すときもfind
で探して、呼び出すだけですので。
今回の難しいところは、拡張式の FactoryMethod の作り方よりも、関数ポインタ、std::map、を理解しているかどうかだと思います。さらに言ってしまえば、std::map は今回採用しているだけなので、何か key を渡されたときに、それに関係する関数ポインタを返せるならどう作ってもいいわけです。なので、今回一番重要なのは、関数ポインタを理解しているかどうかなのだと思いました。
最後に、コードと参考資料を載せておきます。
- 作者: マーティン・レディ,Martin Reddy,三宅陽一郎,ホジソンますみ
- 出版社/メーカー: SBクリエイティブ
- 発売日: 2012/10/31
- メディア: 単行本
- 購入: 4人 クリック: 106回
- この商品を含むブログ (11件) を見る