【Unity】ファクトリーメソッドパターン

ファクトリーメソッドパターン(Factory Method Pattern)は、ソフトウェアデザインパターンの一つで、特にオブジェクトの生成に関するパターンです

プログラミングでは次のようなコードでInatantiateメソッドを使い、シーン上にゲームオブジェクトを作成します

使われ方

public GameObject prefabToInstantiate; // インスペクターでプレハブをアタッチします

void Start()
{
    // プレハブをインスタンス化
    GameObject newObject = Instantiate(prefabToInstantiate);

    // 新しいオブジェクトの位置を設定(オプション)
    newObject.transform.position = new Vector3(0f, 0f, 0f);

    // 新しいオブジェクトの回転を設定(オプション)
    newObject.transform.rotation = Quaternion.identity;

    // 新しいオブジェクトを親オブジェクトに設定(オプション)
    newObject.transform.parent = someParentGameObject.transform;
}

内部で実装されている仕組み

public static T Instantiate<T>(T original) where T : Object

Instantiate<T>メソッドは、一般的にファクトリーメソッドデザインパターン(Factory Method Design Pattern)に関連しています。このデザインパターンは、オブジェクトの生成を担当する専用のファクトリーメソッドを提供し、クライアントコードから直接オブジェクトを生成するのではなく、ファクトリーメソッドを介してオブジェクトを生成します。

Instantiate<T>メソッドは、<T>の型パラメータを受け取り、その型のインスタンスを生成するためのファクトリーメソッドのようなものです。このメソッドを使用することで、オブジェクトの生成ロジックをカプセル化し、クライアントコードから生成プロセスを隠蔽することができます。このアプローチは、コードの保守性を向上させ、クライアントコードと生成ロジックを分離するために役立ちます。

したがって、Instantiate<T>メソッドを使用する際は、ファクトリーメソッドデザインパターンを採用していると考えることができます。このパターンは、オブジェクトの生成に関連する柔軟性と拡張性を提供するために一般的に使用されます。

C#でシミュレートしてみる

基本サンプル

Instantiate<T> メソッドは、C#のジェネリクスを使用して、特定の型 T のインスタンスを生成するために使用されます。以下は、Instantiate<T> メソッドの基本的な使い方です。

using System;

public class Program
{
    static void Main()
    {
        // T を指定して、インスタンスを生成したいクラスや型を指定します
        MyClass myObject = Instantiate<MyClass>();

        // 生成されたインスタンスを使用できます
        myObject.SomeMethod();
    }
}

public class MyClass
{
    public void SomeMethod()
    {
        Console.WriteLine("これは MyClassWithのメソッドです");
    }
}

public static class Factory
{
    // Instantiate<T> メソッドを定義します
    public static T Instantiate<T>() where T : new()
    {
        return new T();
    }
}

この例では、Instantiate<T> メソッドを使用して MyClass のインスタンスを生成しています。このメソッドは、new() 制約を持つジェネリック型 T を受け取り、新しい T のインスタンスを生成して返します。

Factory クラス内の Instantiate<T> メソッドは、インスタンス生成の責任を持っており、クライアントコードは直接 new キーワードを使用せずにインスタンスを生成できます。この方法により、クライアントコードと生成ロジックを分離し、柔軟性と保守性を向上させることができます。

Instantiate<T> メソッドは、任意のクラスや型に対して使用でき、ジェネリックであるため、再利用性が高く、型の安全性を保持できる利点があります。

引数を持つサンプル

string型の引数がある場合で考えてみましょう

string 型の引数を受け取る Instantiate<T> メソッドを追加するには、ジェネリック メソッドの型制約を拡張し、T のインスタンスを生成する際に string 引数を渡す方法を提供することが必要です。以下はその例です。

using System;

public class Program
{
    static void Main()
    {
        // string 型の引数を指定して、インスタンスを生成したいクラスや型を指定します
        MyClassWithParameter myObject = Instantiate<MyClassWithParameter>("Hello, World!");

        // 生成されたインスタンスを使用できます
        myObject.SomeMethod();
    }
}

public class MyClassWithParameter
{
    private string message;

    public MyClassWithParameter(string message)
    {
        this.message = message;
    }

    public void SomeMethod()
    {
        Console.WriteLine("これは MyClassWithParameterのメソッドです");
        Console.WriteLine("Message: " + message);
    }
}

public static class Factory
{
    // Instantiate<T> メソッドを定義します。string 引数を追加します。
    public static T Instantiate<T>(string argument) where T : new()
    {
        if (typeof(T) == typeof(MyClassWithParameter))
        {
            // MyClassWithParameter の場合、string 引数を渡してインスタンスを生成
            return (T)Convert.ChangeType(new MyClassWithParameter(argument), typeof(T));
        }
        // 他の型の場合は単に新しいインスタンスを生成
        return new T();
    }
}

この例では、Instantiate<T> メソッドが string 型の引数を受け取るように拡張されています。MyClassWithParameter というクラスを例として示しており、このクラスは string 引数をコンストラクタに受け取ります。Factory クラスの Instantiate<T> メソッドは、T の型が MyClassWithParameter の場合、string 引数を渡してインスタンスを生成します。他の型の場合は通常通り新しいインスタンスを生成します。

このように、string 引数を受け取る Instantiate<T> メソッドを実装することで、異なる型に対して異なる引数を指定してインスタンスを生成することができます。

参考

ファクトリーメソッドパターンの説明リンク

UnityエンジンのInstantiate<T>()メソッドのコードを見てみる

public static T Instantiate<T>(T original) where T : Object
{
    CheckNullArgument(original, "The Object you want to instantiate is null.");
    T val = (T)Internal_CloneSingle(original);
    if ((Object)val == (Object)null)
    {
        throw new UnityException("作成中にクローンが破棄されたため、Instantiate に失敗しました。これはMonoBehaviour.AwakeでDestroyImmediateが呼び出された場合に発生する可能性があります。");
    }

    return val;
}

Unityのオブジェクトを複製するための汎用的な関数 Instantiate<T> を定義しています。この関数はジェネリックメソッドで、T という型パラメータを使用しています。以下はコードの主要な部分を説明します:

Instantiate<T>(T original) where T : Object

ジェネリックメソッド Instantiate を定義しています
T はジェネリック型パラメータで、指定された型のオブジェクトを複製するために使用されます。

where T : Object

T 型がUnityの Object クラスまたはその派生クラスであることを指定しています。

CheckNullArgument(original, "The Object you want to instantiate is null.")

渡された original オブジェクトがnullでないことを確認するための関数を呼び出しています。もしoriginal がnullであれば、指定されたエラーメッセージが含まれた例外がスローされます。

T val = (T)Internal_CloneSingle(original)

渡された original オブジェクトを複製するために Internal_CloneSingle 関数を呼び出しています。この複製は型 T にキャストされ、val に格納されます。

if ((Object)val == (Object)null)

val がnullであるかどうかを確認しています
Unityの Object クラスのキャストが行われており、もしval がnullであれば、オブジェクトの複製が正しく作成されなかったことを示す例外がスローされます。

return val

正常に複製されたオブジェクト val を返します。

このメソッドは、Unityのゲーム開発において、オブジェクトを複製して新しいインスタンスを生成するために使用される汎用的なメソッドです。オリジナルのオブジェクトを複製して、新しいオブジェクトを作成するために使用されます。そして、もし複製が失敗した場合、適切なエラーメッセージをスローします。

呼び出されているメソッドの追跡

[NativeMethod(Name = "CloneObject", IsFreeFunction = true, ThrowsException = true)]
private static Object Internal_CloneSingle([NotNull("NullExceptionObject")] Object data)
{
    return Internal_CloneSingle_Injected(MarshalledUnityObject.MarshalNullCheck(data));
}

UnityのC#スクリプトで使用される、オブジェクトの複製を実際に処理するためのプライベートメソッド Internal_CloneSingle を示しています。以下はこのコードの主要な部分を説明します:

[NativeMethod(Name = "CloneObject", IsFreeFunction = true, ThrowsException = true)]

Unityエンジンがネイティブコードで実装された関数を呼び出すための属性(attribute)です
Name = “CloneObject" は、このネイティブ関数の名前を指定しています
IsFreeFunction = true は、この関数がフリー関数であることを示し、特定のクラスに属さないことを意味します
ThrowsException = true は、この関数が例外をスローする可能性があることを示します。

private static Object Internal_CloneSingle([NotNull("NullExceptionObject")] Object data)

プライベートな静的メソッド Internal_CloneSingle を宣言しています
Object 型の data パラメータを受け取ります。このパラメータは、複製対象のオブジェクトを表します
[NotNull(“NullExceptionObject")] は、data パラメータがnullであってはならないことを示す属性です。もしnullの場合、指定されたエラーメッセージが含まれた例外がスローされます。

return Internal_CloneSingle_Injected(MarshalledUnityObject.MarshalNullCheck(data));

内部的に実装された Internal_CloneSingle_Injected メソッドを呼び出して、オブジェクトの複製を実行します
MarshalledUnityObject.MarshalNullCheck(data) は、渡されたオブジェクト data がnullでないことを確認し、適切にマーシャリング(データのシリアル化と逆シリアル化)するための処理を行います。

このメソッドは、Unity内部でオブジェクトの複製を実行するためのネイティブメソッドを呼び出すラッパーメソッドです。Internal_CloneSingle メソッドは、与えられたオブジェクトを複製し、その複製を返します。また、nullチェックと例外のスロー処理も含まれています。このようなプライベートメソッドは、Unityの内部処理において使用され、通常は開発者には直接公開されません。

呼び出されているメソッドをさらに追跡

private static extern Object Internal_CloneSingle_Injected(IntPtr data);

UnityのC#スクリプトで使用されるプライベートな静的メソッド Internal_CloneSingle_Injected の宣言を示しています
このメソッドは extern キーワードを使用して宣言されており、実際の実装は外部のネイティブコードで提供されています。以下はこのコードの主要な部分を説明します

extern キーワード

このメソッドの実装がC#コードではなく、外部のネイティブコードで提供されることを示します
このメソッドがUnityのネイティブコードにリンクされていることを示します

戻り値の型(Object)

Object 型を返します
このメソッドはオブジェクトの複製を表す Object インスタンスを返すことを期待しています。

引数(IntPtr data)

ネイティブコードに渡され、ネイティブ側で処理されるデータへのポインタを表します
このポインタは、オブジェクトの情報をネイティブ側で処理するために使用されます

このメソッドは、C#コードから呼び出され、ネイティブコードにデリゲートされたタスクを実行します。具体的には、Unityの内部でオブジェクトの複製を実行するために使用され、内部的なリソースの管理やオブジェクトの複製処理がネイティブコードで行われるため、外部からは詳細な実装の詳細を知る必要はありません。開発者は通常、このようなネイティブメソッドを直接呼び出すのではなく、C#の高水準のAPIを使用してUnityオブジェクトを操作します