LoginSignup
7

More than 3 years have passed since last update.

【Unity】ZenjectのTutotialをやってみた(導入)

Last updated at Posted at 2017-12-17

はじめに

【Unity】Zenjectをちょっと触ってみた を拝見していたがTutorial自体がアップデートされていたでの、自分でZenjectの公式Tutotialを行ってみた。

公式:https://github.com/modesttree/zenject

Zenjectとは

Dependency Injection Framework for Unity3D

Unity用の依存性注入のフレームワーク
あんまりどういった意図があるのか理解できていません
IntroductionとTutorialを通して有用性を理解したいところ

では、以下公式の和訳を載せていきながらまとめていく

Introduction

Zenjectは、Unityを対象とするために特別に構築された軽量の依存性注入フレームワークです(Unityの外でも使用できます)。アプリケーションを、細分化された責任を持って疎結合された部品の集合に変えるのに使用できます。 Zenjectは、さまざまな構成でパーツを結合することができ、スケーラブルで非常に柔軟な方法でコードを簡単に作成、再利用、リファクタリング、テストすることができます。
下記のプラットフォームに対応しています

  • PC/Mac/Linux
  • iOS
  • Android
  • Webplayer
  • WebGL
  • PS4 (with IL2CPP backend)
  • Windows Store (including 8.1, Phone 8.1, Universal 8.1 and Universal 10 - both .NET and IL2CPP backend)

IL2CPPをサポートしています。しかしながらいくつかのgotchas(引っ掛け?)があります。 - 詳しくは こちらを参照してください。

なるほど?疎結合のパーツを作成して、それらを簡単に結合の管理をしてくれるのかな?

Features

  • Injection(注入)
    • 通常のC#クラスとMonobehaviorクラスをサポートしている
    • コンストラクタでの注入 (複数のコンストラクタがあればタグ付けすることができます)
    • フィールド変数への注入
    • プロパティーへの注入
    • メソッドへの注入
  • 条件付きBinding (Typeや名前などによる)
  • オプションの依存関係
  • ファクトリを使用した初期化後のオブジェクト作成をサポート
  • ネストされたコンテナ(サブコンテナ)
  • あるシーンから次のシーンに情報を渡すなどの異なるUnityシーンにまたがる注入
  • シーンの生成時、あるシーンが別のシーンからバインディングを継承できるようにする
  • グローバルなプロジェクト全体のバインディングをサポートし、すべてのシーンの依存関係を追加する
  • クラス名、名前空間、または他の基準に基づく規約に基づくバインディング
  • 編集時にオブジェクトグラフを検証する機能(ファクトリで作成された動的オブジェクトグラフを含む)
  • ZenjectBindingコンポーネントを使用してシーン内のコンポーネントに対する自動バインディング
  • Moqライブラリを使用した自動モッキング
  • メモリプールをビルトインでサポート
  • LazyInject<>構造を使用した即時解決
  • マルチスレッドによる解決/生成をサポート
  • 直接修正によって生成されたアセンブリによって、コストの高いリフレクション作業を排除するreflection bakingをサポート
  • ZenAutoInjecterコンポーネントを使用したGameObjectへの自動注入

この時点では何のことかさっぱりだ。。。

Installation

  1. release page
  2. アセットストア

OptionalExtrasフォルダーは不要であればプロジェクトに含める必要はありません。(Signal、TestFrameworkなど)

History

依存性注入(DI)とは?

理論

以下は、私の視点から見た依存性注入の概要です。しかし、それは明確さを保つようにしているので、その背後にある理論について書いては、他の多くの人の文献を探すことを強く勧めます。

ある機能を達成するために個々のクラスを書くとき、その目的を達成するためにシステム内の他のクラスとやり取りする必要があるでしょう。これを行う1つの方法は、具体的なコンストラクタを呼び出して、クラス自体に依存関係を作成させることです。

foo.cs
public class Foo
{
    ISomeService _service;

   public Foo()
    {
        _service = new SomeService();
    }

   public void DoSomething()
    {
        _service.PerformTask();
        ...
    }
}

これは小規模なプロジェクトではうまくいきますが、プロジェクトが成長するにつれて扱いにくくなります。クラスFooは、クラス「SomeService」と緊密に結合されています。後で、別の具体的な実装を使用することを決めたら、Fooクラスに戻ってそれを変更する必要があります。これについて考えた時、最終的にはFooがサービスの特定の実装を選択する詳細を気にしてはいけないことに気づくことがよくあります。 Fooが気にするべきことは、それぞれ独自の責任を果たすことです。 Fooが要求する抽象的なインターフェイスをサービスが満たしている限り、Fooは満足しています。私たちのクラスは次のようになります。

foo.cs
public class Foo
{
    ISomeService _service;

   public Foo(ISomeService service)
    {
        _service = service;
    }

   public void DoSomething()
    {
        _service.PerformTask();
        ...
    }
}

これは優れていますが、Fooを作成しているクラスがあれば、Fooの余分な依存関係を埋め込むという問題があります。

bar.cs
public class Bar
{
    public void DoSomething()
    {
        var foo = new Foo(new SomeService());
        foo.DoSomething();
        ...
    }
}

また、Class Barはおそらく、SomeService Fooの具体的な実装が何を使用しているかについて実際には気にしません。したがって、依存関係がさらに深まります:

bar.cs
public class Bar
{
    ISomeService _service;

   public Bar(ISomeService service)
    {
        _service = service;
    }

   public void DoSomething()
    {
        var foo = new Foo(_service);
        foo.DoSomething();
        ...
    }
}

そのため、アプリケーションの「オブジェクトグラフ」で、どのクラスのどの特定の実装をさらに使用するかを決定する責任があることがわかります。これを極端に捉えて、アプリケーションのエントリーポイントに到達します。この時点で、すべての依存関係が満たされてから開始されます。このアプリケーションのこの部分の依存性注入用語は、「合成ルート」と呼ばれます。通常、次のようになります。

example.cs
var service = new SomeService();
var foo = new Foo(service);
var bar = new Bar(service);
var qux = new Qux(bar);
.. etc.

ZenjectのようなDIフレームワークは、上記のコードのように明示的に行う必要がないように、これらのすべての具体的な依存関係を作成して渡すこのプロセスを自動化するのに役立ちます。

確かにこういった依存関係を把握するのはつらい
あるクラスで new が乱立してたりするので・・・

誤解

DIについては多くの誤解があります。なぜなら最初から全てを理解するのは難しいからです。完全に理解するには経験が必要です。

上記の例に示すように、DIを使用すると、特定のインタフェースの異なる実装(この例ではISomeService)を簡単に交換できます。しかし、これはDIが提供する多くの利点の1つにすぎません。

それよりも重要なのは、Zenjectのような依存性注入フレームワークを使うと、より簡単に「単一責任の原則」に従うことができるという事実です。 Zenjectがクラス間の依存関係を解決することによって、クラス自体は特定の責任を果たすことに集中することができます。

DIを扱う初心者のよくある間違いは、すべてのクラスからInterfaceを抽出し、直接クラスを使用するのではなく、Interfaceを代わりに使用するというものです。目的は、コードをより疎結合にすることです。ですので、具体的なクラスにバインドされるよりも、インターフェイスにバインドされるほうが優れていると考えるのが妥当です。しかしながら、ほとんどの場合、アプリケーションのさまざまな責任には、それらを実装する単一の特定のクラスがあるだけです。このような場合にはインターフェイスを使用することにより、不必要な保守コストが発生します。また、具象クラスにはすでにパブリックメンバによって定義されたインタフェースがあります。代わりに、経験則として、クラスに複数の実装がある場合、または将来複数の実装を行う予定がある場合にのみインターフェイスを作成することをお勧めします。(これは「Reused Abstractions Principle:RAP」として知られています。)

全部をInterface化する必要はないと

その他の利点:

  • リファクタリング性
    • DIを適切に使用する場合のように、コードが疎結合している場合、コードベース全体が変更に対してより弾力性があります。これらの変更を他の部分に混乱させることなく、コードベースの部分を完全に変更することができます。
  • モジュラコードを奨励
    • DIフレームワークを使用する場合、クラス間のインタフェースについて考える必要があるため、より良い設計手法に自然に従います。
  • テスト容易性
    • 自動単位テストやユーザー主導テストの作成は、依存関係を別の方法で配線する別の「構成ルート」を作成するだけなので、非常に簡単になります。 1つのサブシステムだけをテストしたいですか?新しいコンポジションルートを作成するだけです。 Zenjectは、コンポジションルート自体でコードの重複を避けることもサポートしています。

単一クラスのことだけを考えることで上記のようなことが簡単になると

ここまでが、ZenjectとDIの解説。
次から実際のTutorialをこなしたいと思います。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7