【C#】ゲームで学ぶデザインパターン~FactoryMethod~

FactoryMethodとは

FactoryMethodはオブジェクトの生成処理を分離するデザインパターンです。

今回はゲームで敵を生成したい場合を例に解説していきます。

FactoryMethod流れを簡単に書くと

  • 敵生成用のクラス(EnemyCreater)を作る←ここに生成の流れを書く
  • ↑クラスを継承した子クラス(SlimeCreater)を作る←ここに具体的な生成処理を書く
  • スライムを生成する時はSlimeCreaterを使う

こんな感じになります。

一応クラス図も載せておきます(クラス図分からない人は無視でOK!)

FactoryMethodを使わない場合

スライムとゴーレムを生成すると仮定して、まずはFactoryMethodを使わずに実装してみましょう。

class Slime
 {
     public Initialize();
 }
 class Golem
 {
     public Initialize();
 }
 class GameFlow
 {
     public void Start()
     {
         Slime slime = new Slime();
         slime.Initialize();
         Golem golem = new Golem();
         golem.Initialize();
     }
 }

こんな感じになります。

FactoryMethodを使う

それではFactoryMethodを使う形に変えていきましょう

今回作るクラスはこんな感じです。

//生成する敵用のクラス
・EnemyBase・・・敵の基底クラス
・Slime・・・スライム用クラス
・Golem・・・ゴーレム用クラス
//生成用のクラス
・EnemyCreator・・・敵生成用の基底クラス
・SlimeCreator・・・スライム生成用クラス
・GolemCreator・・・ゴーレム生成用クラス
//使う側のクラス
・GameFlow・・・Creatorを使うクラス

まずは生成する敵関連のクラスを見ていきましょう。

EnemyBaseという継承元になるクラスを作成し、SlimeとGolemクラスはそれを継承して作成します。

abstract class EnemyBase
 {
     public abstract void Initialize();
 }
 
 class Slime : EnemyBase
 {
     public override void Initialize()
     {
         //何かしらの初期化処理
     }
 }
 
 class Golem : EnemyBase
 {
     public override void Initialize()
     {
         //何かしらの初期化処理
     }
 }

次に生成用のクラスを作っていきます。

abstract class EnemyCreator
 {
     protected abstract EnemyBase CreateEnemy();
     //ここで生成の流れを記述
     public EnemyBase Create()
     {
         EnemyBase enemy = CreateEnemey();
         enemy.Initialize();
         return enemy;
     }
 }
 
 class SlimeCreator : EnemyCreator
 {
     protected override EnemyBase CreateEnemy()
     {
         //スライム専用で何か処理がある場合この関数で処理が書ける
         return new Slime();
     }
 }
 
 class GolemCreator : EnemyCreator
 {
     public override void Initialize()
     {
         //ゴーレム専用で何か処理がある場合この関数で処理が書ける
         return new Golem();
     }
 }

最後にこれらを使って敵を生成します

class GameFlow
 {
     SlimeCreator slimeCreator = new SlimeCreator();
     GolemCreator golemCreator = new GolemCreator();
     public void Start()
     {
         EnemyBase enemy = slimeCreator.Create();
         EnemyBase enemy = golemCreator.Create();
     }
 }

以上がFactoryMethodを使った実装でした。

FactoryMethodのメリット

生成の大枠は親クラスに記述し、具体的な処理は子クラスがそれぞれ自由に書くようにするので、柔軟性が高いです。

また、生成処理が特定の箇所にまとまっているので、追加・変更が容易になります。

FactoryMethodのデメリット

生成するタイプが増えるとクラスが1つ増えてしまいます。(今回の例の場合敵の種類)

種類が増えていくと、コードの可読性が悪くなり、追加もめんどうになります…

FactoryMethodの使いどころ

オブジェクトを生成しているすべての場所に適応できるパターンですが、使わないほうがシンプルになる時もあるでしょう。

個人的には、以下の点が重なった場合が使い時ではないかと思います。

  • 生成するオブジェクトが複数あり、条件によって生成物が変わる場合
  • ↑で生成したいオブジェクトの生成手順が共通の場合

👇この記事を読んだあなたにお勧めの書籍

コメント

  1. たろう より:

    分かりやすいです。

    要は「インスタンスの生成から初期化までをまとめてやってくれる」のがFactoryの役割ってことですよね。
    もしもFactoryがなかった場合、newしてから、初期化処理までしないといけないので。初期化処理を忘れるヒューマンエラーが起こる可能性がありますもんね。

    で、今回はabstractを使ってますが、interfaceを使うこともありますよね?
    それと、Factoryのクラスをstaticにしてるのも見たことがあります。
    確かに、Factoryは他のクラスを生成するためだけのクラスなので、わざわざインスタンス化しないと使えないようにするのではなく、staticで使えるようにした方がいいのかなと思ったりします。

    まとめると聞きたいことは、
    ・abstractとinterfaceはどちらが主流ですか
    ・Factoryはstaticにするのが主流ですか
    の二点です

    GoF本も読んだことがなくネットの知識だけなので結構曖昧です

    • のっぽ管理人 より:

      たろうさんコメントありがとうございます。
      簡単ではありますが、以下が質問への回答となります。

      >インスタンスの生成から初期化までをまとめてやってくれるのが、FactoryMethodの役割であっているか?
      はい、この認識であっていると思います。
      おっしゃる通り初期化忘れの防止にもなりますし、生成が一か所にまとまっているので、変更があった際の変更箇所を減らすことができます。

      実はFactoryMethod使うメリットとしてもう一つ、依存性の逆転を行うことができるという点があります。
      こちらは難しい話だったので、この記事では解説しておりませんでした。
      下記サイト様などが参考になると思います。
      https://blog.ecbeing.tech/entry/2021/01/20/114000

      >abstractとinterfaceはどちらが主流ですか?
      abstractとinterfaceでは使用用途が違うため、どちらが主流などはないと思います。
      共通化したいものの特徴によって使い分けるのが良いです。
      今回の場合は敵というくくりだったので、abstractを使いました。

      抽象クラス(abstract)とinterfaceの違いを調べてみるといいかもしれません。
      下記サイト様などが参考になると思います。
      https://4engineer.net/oo/abstract-vs-interface/

      >Factoryはstaticにするのが主流ですか?
      すべての生成関数をstaticにするのはお勧めしません。
      どこからでもアクセスできる状態は便利ではありますが、管理面ではあまり好ましくないです。
      特定の場所で管理してそれを使って生成する方が良いかと思います。
      ただどうしても頻繁にいろんなところからアクセスしないといけない場合などはstatic関数にしてしまうのも手だとは思います。

      ご参考になれば幸いです!