Factory Method を使って、スライム属を作る
Factroy Method
をドラクエのスライムで表現してみる記事。
Factory Method とは
オブジェクト指向における再利用のためのデザインパターンの一つです。そのデザインパターンの中でも生成に関するパターン
の一つ。
このパターンによって、オブジェクト型を指定せずにオブジェクトを生成できるようになります。
簡単に説明すると、関数内でコンストラクタを呼び、生成したオブジェクトを返すのがこのパターンの基本になります。
これに継承を組み合わせることで、動的にオブジェクトを生成することができるようになります。
で、この FactoryMethod
を使って、いろんなスライムを作るというのが今回のお話です。
ちなみに、抽象基本クラスを分かっておく必要があるので、分からない方は、調べるか、以下を参考にしてみるといいでしょう。
pickles-ochazuke.hatenablog.com
全体イメージ
UML 図を初めて書いたので、間違っているところがあるかも……。
まずは大きく4つに分けて、ISlime
, Slime
, SheSlime
, HealSlime
, Status
。一つずつ簡単に説明します。
Status
これは、スライムのステータスを表します。日本の(RPG)ゲームでいうステータスと英語の Status は、意味が異なるようなのだけど、
ここでは深く考えないようにします。中身は、見てみると何となく分かると思うのだけど、名前(name_
)、HP(hp_
)、MP(mp_
)、攻撃力(attack_
)、防御力(defense_
)、経験値(exp_
)、ゴールド(gold_
)が入っている。モンスター(今回は、スライム属のみ)共通のステータスとなります。
ISlime
スライム属の抽象基本クラスです。ISlime
の I
は、Interface
の I
。
スライム属に属するクラスは、これを継承します。これによってあとで作る FactoryMethod
は、一つの関数で動的にオブジェクトを作れるようになります。name()
, hp()
, mp()
, attack()
, defense()
, exp()
, gold()
は、get 関数で特に意味はなくて、status_
にアクセスする手段を用意しているだけ。あと、コンストラクタ、デストラクタは、実際にはあるのだけど、書いてないです。これは、Slime
, SheSlime
, HealSlime
共通。
Talk()
, Skill()
は、それぞれ純粋仮想関数。継承先は、この二つの処理を実装する必要があります。
ViewInformation()
は、オブジェクトのステータスを出力します。
Slime
スライムを表すクラス。Talk()
, Skill()
をオーバライドしています(あとデストラクタも)。ここら辺は、実行したときにちゃんと Slime のクラスが作られているか分かるようにしているだけなので、実装内容は何でもいいです。
SheSlime
ベススライムを表すクラス。外国だと SheSlime と言うみたいです。内部の説明は、Slime と同じ。もちろん実装内容は、区別できるようにしてあります。
HealSlime
ホイミスライムを表すクラス。これも Slime と同じ。
以上のスライムクラスを使って、FactoryMethod
を作ります。
FactoryMethod
まずは、クラス図を
FactroyMethod を実装するのは、SlimeFactory
というクラス。このクラスは、今回、コンストラクタもデストラクタも必要ないので、delete
しています。DoMakeSlime()
という関数が FactoryMethod
になります。
実際は、FactoryMethod を実装しているクラス内でオブジェクトの管理やシングルトンパターンを実装するようだけど、簡単にするためにそれらは実装していません。
というか生成と管理を一つのクラスで任せるものなのかどうか分からないです……。別々にしても良さそうだけど、どうなんだろう?
クラス図の"関連"を表す矢印の使い方が正しいのか自信はないのだけど、これらをインクルードする必要があるので、"関連"としています。
ただ、ヘッダ(SlimeFactory.h)でインクルードするのは、ISlime のみで、他はソースファイル(SlimeFactroy.cpp)でインクルードしています。
実装
実装内容は以下のようになっています。まずは、ヘッダファイル。
#pragma once #include <string> #include "ISlime.h" class SlimeFactory { public: SlimeFactory() = delete; virtual ~SlimeFactory() = delete; static ISlime* DoMakeSlime(const std::string& type); };
スライム属のクラスの内、インクルードしているのは、 “ISlime.h” のみで、他のクラスはここで知る必要はないです。
コンストラクタとデストラクタは必要なかったので、delete しています。デストラクタを仮想にしているけど必要ないです……。
DoMakeSlime(const std::string& type)
が先に書いたように FactroyMethod になります。type
の値を見て、どのオブジェクトを作るかを決めています。作りたいオブジェクトが分かるなら文字列
でも enum 型
でもただの値
でもいいです。たぶん enum 型
が一番扱いやすいです。
次にソースファイル。
#include "SlimeFactory.h" #include "Slime.h" #include "SheSlime.h" #include "HealSlime.h" 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(); } return nullptr; }
こちらでは、実際にオブジェクトを作るために型を知る必要があるので、Slime.h
, SheSlime.h
, HealSlime.h
をインクルードしています。
DoMakeSlime
の処理は、とても簡単で type
の中身を見て、条件式で判定して、その中のオブジェクト生成処理を実行し、そのオブジェクトを返すだけです。型の異なるオブジェクトを返せるのは、継承元が全て同じ ISlime
だから。
戻り値の型を void*
にすれば、継承元が異なる型を返すようにすることも出来るけど、危険だろうし、扱いづらい。それなら抽象基本クラスごとに関数を分けた方がいいと思います。
次は、実際に使うときのコードとその結果を載せます。
実行
main.cpp 内は、以下のようになっています。
#include <iostream> #include "ISlime.h" #include "SlimeFactory.h" int main(void) { ISlime* slime = SlimeFactory::DoMakeSlime("Slime"); slime->Skill(); delete slime; slime = SlimeFactory::DoMakeSlime("SheSlime"); slime->Skill(); slime->ViewInformation(); delete slime; slime = SlimeFactory::DoMakeSlime("HealSlime"); slime->Skill(); delete slime; slime = SlimeFactory::DoMakeSlime(""); if (!slime) { std::cout << "スライムの生成に失敗した!" << std::endl; std::cout << std::endl; } else { slime->Skill(); delete slime; } return 0; }
実行する側は、FactoryMethod のクラスと、抽象基本クラスだけを知るだけで使うことができます。もちろん、継承先にしかない機能を使う場合は、そのクラスのヘッダファイルをインクルードする必要があります。
実行結果は、以下のようになります。
スラりん が現れた! スラりん のスキル発動! 逃げ出す! しかし、逃げられなかった! へんじがない。ただのスラりんのようだ。 スラみ のスキル発動! メラ! しかし、MP が足りない! class SheSlime の情報を表示します ======== スラみ 8 3 10 12 3 1 =============================== ホイミン のスキル発動! ホイミ! ホイミン に 30 の回復! スライムの生成に失敗した! 続行するには何かキーを押してください . . .
おわり
以上、FactoryMethod の基本を書いてみました。上手くクラスを抽象化できれば、FactoryMethod が有効活用できそうです。FactoryMethod を扱うことよりもクラスの抽象化をどうするかの方が難しいのかもしれないですね。
作成したオブジェクトを FactroyMethod の内部(今回だと SlimeFactory)で持つか、作成を依頼してきた側(main関数)に任せるかは、その時々で変わりそう。
最後に、今回のコードを以下に上げておきます。
参考にした資料は以下です。
- 作者: マーティン・レディ,Martin Reddy,三宅陽一郎,ホジソンますみ
- 出版社/メーカー: SBクリエイティブ
- 発売日: 2012/10/31
- メディア: 単行本
- 購入: 4人 クリック: 106回
- この商品を含むブログ (11件) を見る
- 作者: エリックガンマ,ラルフジョンソン,リチャードヘルム,ジョンブリシディース,Erich Gamma,Ralph Johnson,Richard Helm,John Vlissides,本位田真一,吉田和樹
- 出版社/メーカー: ソフトバンククリエイティブ
- 発売日: 1999/10
- メディア: 単行本
- 購入: 21人 クリック: 711回
- この商品を含むブログ (210件) を見る