【C#,Unity】メソッドチェーン

メソッドチェーン(Method Chaining)は、複数のメソッド呼び出しを連続して行うテクニックで、フルーエントインターフェース(Fluent Interface)のスタイルでよく使用されます。このスタイルは、コードの可読性を向上させることができ、より宣言的なプログラミングを促進します。

メソッドチェーンを実装するためには、各メソッドがオブジェクト自身を返すように設計します。これにより、次のメソッド呼び出しが可能となります。

C#でのサンプル

以下は、簡単なメソッドチェーンを使用するC#の例です。ここではStringBuilderクラスを使用していますが、カスタムクラスで同様のアプローチを取ることもできます。

ライブラリのStringBuilderクラスでのメソッドチェーン

using System.Text;

public class Program
{
    public static void Main()
    {
        StringBuilder sb = new StringBuilder();
        sb.Append("Hello, ")
          .Append("world!")
          .AppendLine()
          .Append("This is a method chaining example.");

        Console.WriteLine(sb.ToString());
    }
}

カスタムクラスでのメソッドチェーン

カスタムクラスにメソッドチェーンを実装する場合、各メソッドがクラスのインスタンスを返すようにします。例えば、設定を行うクラスを以下のように定義することができます。

public class Car
{
    public string Color { get; private set; }
    public string Model { get; private set; }

    public Car SetColor(string color)
    {
        Color = color;
        return this;
    }

    public Car SetModel(string model)
    {
        Model = model;
        return this;
    }

    public override string ToString()
    {
        return $"Color: {Color}, Model: {Model}";
    }
}
public class Program
{
    public static void Main()
    {
        Car car = new Car()
            .SetColor("Red")
            .SetModel("Toyota");

        Console.WriteLine(car);
    }
}

この例では、Carクラスの各メソッドは自身のインスタンスを返しているため、メソッドチェーンを形成することができます。このテクニックは、特にオブジェクトの設定や構築を行う際に役立ちます

Unityでのサンプル(その1)


Unityでメソッドチェーンを使ったサンプルを作るには、独自の機能を持つクラスを定義して、それぞれのメソッドがこのクラスのインスタンスを返すようにします。ここでは、簡単なゲームオブジェクトの設定を行うカスタムクラスを使用したメソッドチェーンの例を示します。

シンプルなメソッドチェーンの例

using UnityEngine;

public class GameObjectConfigurer
{
    private GameObject gameObject;

    public GameObjectConfigurer(GameObject gameObject)
    {
        this.gameObject = gameObject;
    }

    public GameObjectConfigurer SetPosition(Vector3 position)
    {
        gameObject.transform.position = position;
        return this;
    }

    public GameObjectConfigurer SetScale(Vector3 scale)
    {
        gameObject.transform.localScale = scale;
        return this;
    }

    public GameObjectConfigurer SetName(string name)
    {
        gameObject.name = name;
        return this;
    }

    public GameObject Apply()
    {
        return gameObject;
    }
}

Unity スクリプトでの使用(サンプル1)

上記のGameObjectConfigurerクラスを使って、シーン内のゲームオブジェクトの設定をチェインメソッドで行います。

using UnityEngine;

public class ExampleUsage : MonoBehaviour
{
    void Start()
    {
        GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube);

        new GameObjectConfigurer(cube)
            .SetName("MyCube")
            .SetPosition(new Vector3(0, 0, 0))
            .SetScale(new Vector3(2, 2, 2))
            .Apply();  // チェインの最後で Apply() を呼び出し、設定を適用
    }
}

解説

この例では、GameObjectConfigurerクラスがUnityのGameObjectをラップし、その位置、スケール、名前を設定するメソッドチェーンを提供しています。Apply()メソッドは最後に呼び出して、変更を適用します。これにより、ゲームオブジェクトの設定が非常に読みやすく、簡潔に行えるようになります。

このパターンは、Unityでのオブジェクトの設定を簡略化し、コードの可読性を向上させるために役立ちます。また、複数の設定を一連のチェーンとして一度に行うことで、コードの整理も行いやすくなります。

(参考)最後のApplyメソッドは必ず要りますか?

最後の Apply() メソッドは、この特定の例では必須ではありませんが、それがある理由にはいくつかの利点があります。このメソッドの利用は設計上の選択であり、それを使うかどうかは、クラスの使用目的と設計によって異なります。

Apply() メソッドを使用する理由

  1. 明示的な適用: Apply() メソッドは、設定が完了したことを明示的に示し、オブジェクトが使用の準備ができたことを確認します。これは、設定プロセスが複数のステップを含む場合や、特定の設定後に追加の初期化や計算が必要な場合に特に有用です。
  2. 分離と整理: このメソッドは設定のロジックとオブジェクトの使用開始を明確に分離します。これにより、設定プロセスが完了していることをコード上で明確にし、その後の処理を容易に理解しやすくします。
  3. 条件付き適用: 設定が一定の条件を満たす場合のみ適用したいシナリオで役立ちます。たとえば、すべての設定が正しく行われた場合にのみ、オブジェクトの状態を「準備完了」とマークする処理を Apply() 内に記述することができます。

Apply() メソッドが不要な場合

  • シンプルな設定の場合: オブジェクトの設定が単純であり、追加の処理を伴わない場合は、Apply() メソッドを省略し、各設定メソッドが直接オブジェクトに変更を適用するようにすることができます。これにより、コードがさらにシンプルになります。
  • リアルタイムでの適用が必要な場合: 設定をリアルタイムで適用し、即時に結果を反映させたい場合(例えば、ユーザーインターフェースの動的な更新など)は、Apply() メソッドを使用せずに、各設定メソッドがその場で変更を適用するように設計することが望ましいかもしれません。

結論

Apply() メソッドの使用は、そのクラスの設計意図と使用環境によります。それが必要かどうかは、どのようにしてそのクラスを使用したいか、または何を達成したいかに基づいて決定することが最善です。設計の選択においては、クリアで一貫性のあるAPIを提供することが重要です。

Unityでのサンプル(その2)

Unityでより実用的なシナリオを想定したメソッドチェーンの例を示します。この例では、ゲーム内のキャラクターに様々な状態を設定するためのクラスを作成し、それをメソッドチェーンで操作する方法を紹介します。

キャラクター設定クラス

以下のCharacterConfiguratorクラスは、キャラクターの名前、体力、攻撃力などの属性を設定するために使用します

using UnityEngine;

public class CharacterConfigurator
{
    private GameObject character;

    public CharacterConfigurator(GameObject character)
    {
        this.character = character;
    }

    public CharacterConfigurator SetName(string name)
    {
        character.name = name;
        return this;
    }

    public CharacterConfigurator SetHealth(int health)
    {
        character.GetComponent<CharacterStats>().health = health;
        return this;
    }

    public CharacterConfigurator SetStrength(int strength)
    {
        character.GetComponent<CharacterStats>().strength = strength;
        return this;
    }

    public GameObject Build()
    {
        return character;
    }
}

public class CharacterStats : MonoBehaviour
{
    public int health;
    public int strength;
}

Unity スクリプトでの使用

CharacterConfiguratorクラスを使用して、新しいキャラクターを設定し、その設定をゲーム内で適用します。

using UnityEngine;

public class GameSetup : MonoBehaviour
{
    void Start()
    {
        GameObject character = new GameObject();

        // キャラクターにコンポーネントを追加
        character.AddComponent<CharacterStats>();

        // メソッドチェーンを使用してキャラクターを設定
        new CharacterConfigurator(character)
            .SetName("Warrior")
            .SetHealth(100)
            .SetStrength(75)
            .Build();  // 最終的なGameObjectを取得して追加の設定や使用が可能

        // ゲームオブジェクトをシーンに追加するための追加のロジックがあればここに記述
    }
}

解説

この例では、CharacterConfiguratorクラスを介して、UnityのGameObjectにCharacterStatsコンポーネントを追加し、そのプロパティを設定しています。Build()メソッドは最終的なGameObjectを返すため、これを利用して更に他の設定や操作を行うことができます。メソッドチェーンは設定の流れを簡潔かつ明確に表現し、コードの可読性と管理のしやすさを向上させます。

参考)メソッドチェーンで使われているデザインパターン

メソッドチェーンを使用するクラスの設計においては、主に以下のデザインパターンが活用されています:

ビルダーパターン (Builder Pattern)

ビルダーパターンは、複雑なオブジェクトの構築過程をステップバイステップで行うことができるようにするパターンです。このパターンを使用すると、最終的なオブジェクトを返す前に、様々な設定を逐次適用することが可能です。メソッドチェーンは、このパターンの一部としてしばしば用いられ、各メソッドが次のメソッド呼び出しのために自身のインスタンス(this)を返すことで、フルーエントなインターフェイスを提供します。

フルーエントインターフェース (Fluent Interface)

フルーエントインターフェースは、コードの可読性と流れを改善するために設計されたパターンです。このスタイルでは、オブジェクトのメソッドが連鎖的に呼び出され、自然言語に近い形式でコードを書くことができます。これにより、コードの意図が明確になり、他の開発者が理解しやすくなります。

チェーン・オブ・レスポンシビリティ (Chain of Responsibility)

このパターンは、一連のオブジェクト(ハンドラー)がリクエストを処理する責任を持ち、各ハンドラーがリクエストを処理できない場合には次のハンドラーに渡す仕組みです。メソッドチェーンは、このパターンの形式的なアプローチとは異なりますが、リクエスト(ここでは設定やコマンド)が連続して処理される点で類似しています。

ファサードパターン (Facade Pattern)

メソッドチェーンの使用は、複雑なシステムやライブラリの単純なインターフェイスを提供するという点でファサードパターンの考え方とも関連があります。このインターフェイスを通じて、ユーザーは複雑な内部ロジックを意識せずに機能を利用できます。

これらのパターンは、特にメソッドチェーンを利用することで、コードの構造を明確にし、拡張性とメンテナンスの容易さを向上させるために有効です。特にビルダーパターンとフルーエントインターフェースは、メソッドチェーンを使う際に最も一般的に関連するパターンです。

フルーエントインターフェース(Fluent Interface)の詳細

定義: フルーエントインターフェースは、ソフトウェアの利用者がコードを自然な言語に近い形で連続して呼び出せるようにするためのオブジェクト指向APIの設計方法です。このアプローチは、メソッドチェーンを用いて実装されることが多く、各メソッドが処理後に自身のオブジェクトを返すことで、連続したメソッド呼び出しが可能となります。

利点:

  • 可読性の向上: メソッドの連鎖が自然言語に近い流れで記述できるため、コードの意図が読み取りやすくなります。
  • 使いやすさ: 利用者はメソッドを連続して呼び出すことができるため、コードの記述量が減少し、より簡潔で理解しやすいコードを書くことが可能です。
  • 柔軟性の向上: 設定値や処理の順番を自由に変更できるため、APIの利用者がより柔軟に機能をカスタマイズできます。

public class StringBuilder
{
    private List<string> _strings = new List<string>();

    public StringBuilder Append(string value)
    {
        _strings.Add(value);
        return this; // 自身のインスタンスを返す
    }

    public override string ToString()
    {
        return String.Join("", _strings);
    }
}

var builder = new StringBuilder();
string result = builder.Append("Hello, ").Append("world!").ToString();

この例では、StringBuilderクラスがフルーエントインターフェースを使用しており、.Append()メソッドが連続して呼び出されています。それぞれの呼び出しが同じオブジェクト(StringBuilderのインスタンス)を返すため、このようなチェーンが可能です。

まとめ

メソッドチェーンとフルーエントインターフェースの設計は、特にAPIやフレームワークを提供する際に非常に有効であり、開発者が直感的かつ効率的にコードを記述できるように支援します。このような設計パターンは、特にライブラリやフレームワークの開発者にとって有益です。

Unity

Posted by hidepon