【デザインパターン】Factory Methodを学ぶ【C#】

C#

一言で言うと…

  • 変更に強い(拡張しやすい)インスタンス生成処理の設計パターン

概要

Factory Method パターンは、GoF デザインパターンの一つで、生成に関するデザインパターンです。

生成対象のクラス(Product)のインスタンス化を、生成側クラス(Creator) のサブクラスが提供するメソッド(ファクトリメソッド)を通じて行うことで、インスタンス生成の責務を生成側のサブクラスに委譲することができます。

責務を適切に委譲することで、以下の利点が得られます:

  • クライアント側が具体的なクラス(Concrete Product)に依存しない設計になる。
  • クラスの再利用性を向上させ、拡張しやすい構造にできる。

これにより、「新しい種類のオブジェクトを追加しても既存のコードに影響を与えにくい」作りにできるのです。


また、Factory Method パターンは、SOLID 原則の一つである DIP(Dependency Inversion Principle: 依存性逆転の原則) に則った設計を可能にします。

例えば、クライアント側が具体的な実装(Concrete Product)ではなく、抽象的なインターフェース(Product)に依存することで、高い柔軟性を持つコード設計を実現できます。

構成

classDiagram
	direction BT
	namespace creator {
		class Creator {
			<< abstract >>
			+ anOperation()
			# factoryMethod() IProduct
		}
		class ConcreteCreator {
			# factoryMethod() IProduct
		}
	}
	ConcreteCreator --|> Creator : extends
	
	namespace product {
		class IProduct {
			<< interface >>
		}
		class ConcreteProduct
	}
	ConcreteProduct ..|> IProduct : implements
	
	Creator ..> IProduct
	ConcreteCreator ..> ConcreteProduct
	
	Client ..> ConcreteCreator : << use >>
	
	note for ConcreteCreator "return new ConcreteProduct()"

実装例(C#)

クラス概要

早速実装例です。今回はC#でゲーム開発を行う場合を想定してみました。

  • IGameEntity:ゲーム内エンティティ共通インターフェース(Product)
  • Enemy, Item:具象エンティティ(Concrete Product)
  • GameEntityFactory:抽象ファクトリ(Creator)
  • EnemyFactory, ItemFactory:具象ファクトリ(Concrete Creator)
  • クライアント:Factory を使ってエンティティを生成・使用

ソースコード

1. IGameEntity インターフェース(Product)

C#
/// <summary>
/// ゲーム内エンティティ共通インターフェース(Product)
/// </summary>
public interface IGameEntity
{
    /// <summary>
    /// 生成時のアクション
    /// </summary>
    void OnSpawn();
}

これはゲーム内エンティティの共通インターフェースです。Factory Method パターンの Product に位置します。

ゲーム内に生成される要素はすべてこのインターフェースを実装することで、敵もアイテムも共通の「ゲームエンティティ」として扱うことができます。

共通の振る舞いとして、OnSpawn() で生成時のアクションを定義することができます。

2. 具象エンティティ(Concrete Product)

C#
using System;

/// <summary>
/// 具象エンティティ(Concrete Product)
/// </summary>
public class Enemy : IGameEntity
{
    public void OnSpawn()
    {
        Console.WriteLine("エネミーがあらわれた!");
    }
}


public class Item : IGameEntity
{
    public void OnSpawn()
    {
        Console.WriteLine("アイテムが発生した!");
    }
}

これらのクラスはゲーム内エンティティの具象クラスです。Factory Methodパターンの Concrete Product に位置します。

今回は敵(Enemy)と、アイテム(Item)を作成してみました。どちらも IGameEntity インターフェースを実装しているので、OnSpawn() の処理実装が必須となります。

3. 抽象ファクトリ(Creator)

C#
using System;

/// <summary>
/// 抽象ファクトリクラス(Creator)
/// </summary>
public abstract class GameEntityFactory
{
    /// <summary>
    /// エンティティインスタンスを作成する(Factory Method)
    /// </summary>
    /// <returns>エンティティ</returns>
    protected abstract IGameEntity CreateEntity();

    /// <summary>
    /// エンティティを生成する
    /// </summary>
    public void SpawnEntity()
    {
        IGameEntity entity = CreateEntity();
        Console.WriteLine("エンティティ生成中...");
        entity.OnSpawn();
    }
}

これはエンティティ生成を行うファクトリの抽象クラスです。Factory Method パターンの Creator に位置します。

Factory Method である CreateEntity() を通して、エンティティの生成を抽象化しています。

エンティティの生成を抽象化することで、SpawnEntity() を共通化することができています。

4. 具象ファクトリ(Concrete Creator)

C#
/// <summary>
/// 具象ファクトリ(Concrete Creator)
/// </summary>
public class EnemyFactory : GameEntityFactory
{
    protected override IGameEntity CreateEntity()
    {
        return new Enemy();
    }
}


public class ItemFactory : GameEntityFactory
{
    protected override IGameEntity CreateEntity()
    {
        return new Item();
    }
}

これらのクラスはエンティティの生成を行うファクトリの具象クラスです。Factory Method パターンの Concrete Creator に位置します。

今回は敵(Enemy)と、アイテム(Item)のファクトリを作成します。どちらも GameEntityFactory を継承しており、抽象メソッド CreateEntity() の処理実装を行っています。

5. クライアントコード

C#
class FactoryMethodSample
{
    static void Main()
    {
        GameEntityFactory enemyFactory = new EnemyFactory();
        GameEntityFactory itemFactory = new ItemFactory();

        // 敵を生成
        enemyFactory.SpawnEntity();

        // アイテムを生成
        itemFactory.SpawnEntity();
    }
}

これはクライアントコードの例です。ゲーム内エンティティを生成する場面を想定しています。

クライアント側は EnemyItem など具体的な生成対象クラスに依存せず、ファクトリの参照だけ持っていますね。

実際に実行してみると、、

Bash
$ mcs *.cs -out:FactoryMethodSample.exe
$ mono FactoryMethodSample.exe 
エンティティ生成中...
エネミーがあらわれた!
エンティティ生成中...
アイテムが発生した!

無事各オブジェクトの生成時処理を行うことができています!💡

使いどころ

  • オブジェクトの型や依存関係が事前に決まっていない場合
    • クライアント側は具体的なクラスを意識せずにオブジェクトを生成できるため、将来的な機能拡張が容易になります。

例えば、新しい種類の Product を追加しても、既存のクライアントコードを修正する必要がありません。

新たなエンティティ(例:NPC, Trap)を追加したい場合は、
IGameEntity を実装したクラス + Factory を用意するだけで OK。

長所・短所

長所

  • Creator(生成側クラス)と Concrete Product(具象プロダクト)の密結合を回避
    • 依存関係を抽象クラスやインターフェースに向けることで、変更に強い設計になります。
  • インスタンス生成処理の一元化(Single Responsibility Principle: 単一責任の原則)
    • Product の作成処理が Creator のファクトリメソッドに集約されるため、生成処理の変更が容易になります。
  • OCP(Open/Closed Principle: オープン・クローズドの原則)の適用
    • 新しい Product を追加しても、既存のクライアントコードには影響を与えません。

短所

  • サブクラスの増加によるコードの複雑化
    • Product の種類が増えるたびに、新しいサブクラスを追加する必要があります。そのため、クラスの数が増えてコードが複雑になる恐れがあります。

まとめ

Factory Method パターンは、オブジェクトの生成をサブクラスに委ねることで、依存関係を整理し、柔軟な設計を実現するデザインパターンです。

  • 再利用性の向上:異なるオブジェクトの生成方法を統一的に管理できる。
  • 拡張性の向上:新しいプロダクト(インスタンス)を追加しても既存コードを変更せずに済む(OCP: 開放閉鎖の原則)。
  • 依存性の制御:DIP(依存性逆転の原則)を適用し、高度なモジュール分離を実現する。

例えば、「作成するオブジェクトの種類が増える可能性がある」「オブジェクト生成の責務を分離したい」といったケースに適しています。

一方で、新しいサブクラスを増やしすぎるとコードが複雑化するため、使うタイミングを見極め、シンプルな設計を意識すること(KISS の原則)が重要になります。

タイトルとURLをコピーしました