Unity Assembly definition Files を利用したより強固なレイヤ定義

# はじめに

よく実装は「疎結合にすべき」といわれます。
そのためにレイヤードアーキテクチャに腐敗防止層を設けたり、依存性逆転の原則を適用したりして、3rdパーティのライブラリの変更から自身のコードを守ることがよく行われています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274974/94faa290-53f6-cae7-295a-abbf9c20818e.png)

# Assembly definition

Unityでは [Assembly definition](https://docs.unity3d.com/Manual/ScriptCompilationAssemblyDefinitionFiles.html)によりアセンブリ空間を定義でき、ディレクトリ構成を基にアセンブリ空間を分離することができます。

レイヤ定義を `namespace` 任せにするだけでなく、アセンブリ空間さえ分離してやることでより他の開発者にレイヤ構成を遵守させることができそうです。

アセンブリ空間分離のお試しプロジェクトを作成しました。

* https://github.com/naninunenoy/AsmdefDependencyPattern

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274974/1aa1b939-fcb9-d340-1fcc-6bfe607bdee0.png)

以下で、レイヤ分離の意図を解説します。

# 詳細は知らない

`Main` はインタフェースの `IDependencee` を所持しており処理を行います。また、`MonoBehaviour` でもあるのでUnityから `Start()` が実行され、 `.DoSomething()` が実行されます。

```C#
public class Main : MonoBehaviour
{
    [Inject] IDependencee dependencee;

    void Start()
    {
        dependencee.DoSomething();
        Debug.Log(dependencee.GetCommonObject().value);
    }
}
```

`Main` が知っているのは interface の `IDependencee` のみであり、実装の詳細である `Dependencee` に関する知識はありません。これは `Dependencee` の実装が今後変更された場合も `Main` には影響が及ばないことを意味しています。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274974/a7f4d1f0-77cc-a5a0-0a93-5f9fa88a3998.png" width="200" />

さらに言えば、 *Dependencer.asmdef* は *DependenceeImplement.asmdef* を参照しないので、*Main.cs* の実装内では `Dependencee` クラスのインスタンスをnewすることすらできません。

*Dependencer.asmdef* が参照するのはいわゆる腐敗防止層である *DependenceeAbstruct.asmdef* になります。
 ここには振る舞いを表すinterfaceのみが定義されています。

```c#
namespace AsmdefDependencyPattern.DependenceeAbstruct
{
    public interface IDependencee
    {
        void DoSomething();
        Common GetCommonObject();
    }
}
```

もちろん *DependenceeImplement.asmdef* は interface を実装するために、*DependenceeAbstruct.asmdef* を参照します。
(上の図にはasmdef参照の矢印は含まれていません)

```c#
namespace AsmdefDependencyPattern.DependenceeImplement
{
    public class Dependencee : DependenceeAbstruct.IDependencee
    {
        public void DoSomething()
        {
            UnityEngine.Debug.Log("Dependencee.DoSomething()");
        }

        public Common GetCommonObject()
        {
            return new Common {value = 999};
        }
    }
}
```

# Dependency Injection

さて、では `Main` に `Dependencee`クラスのインスタンスを渡しているのは何者でしょうか？


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274974/52b02073-e8a0-6214-1b09-0a2e424c02f9.png)

Dependency Injection(DI)によってこの問題を解決しています。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274974/874275e6-f14c-12db-6b58-16f921f48215.png" width="600" />

Unityではおなじみの [Zenject](https://github.com/svermeulen/Extenject) というDIフレームワークを使いました。
`IDependencee` が要求されたときに、`Dependencee` のインスタンスが渡されるように設定を行います。

```c#
public class DependenceIntermediary : Zenject.MonoInstaller
{
    public override void InstallBindings()
    {
        Container.Bind<IDependencee>().To<Dependencee>().AsTransient();
    }
}
```

*DependenceIntermediary.asmdef* は依存性注入のため *DependenceeAbstruct.asmdef* と *DependenceeImplement.asmdef* の両方を参照します。依存性注入を行う特性上、多くの asmdef を参照するようになると思います。

そしてシーン上に `Main` , `DependenceIntermediary` , `Zenject.SceneContext` を配置することで、`SceneContext` により `IDependencee` と `Dependencee` の間の依存関係が解決され、`Main.Awake()` や`Main.Start()` が実行される時には `Dependencee` のインスタンスがフィールドインジェクション経由で取得できる訳です。

# 共通のクラス定義

場合によっては各レイヤを横断するクラスの定義や定数定義が必要になると思われます。
共通の定義は **すべての asmdef から参照される必要があるので、これまでの .asmdef に追加するのでなく、新たに共通定義用の asmdef を作成する必要があります。**

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274974/125a3a49-cbcd-f00e-554f-795dc35940a5.png" width="300" />

仮に、 *Dependencer.asmdef* に共通定義のクラスを作成した場合、*DependenceeAbstruct.asmdef* から *Dependencer.asmdef* を参照する必要が出てきます。*Dependencer.asmdef* は既に *DependenceeAbstruct.asmdef* を参照しているので、この場合循環参照が発生してしまうので破綻してしまいます。(Assembly definitionでは循環参照を設定することはできません)

新たに作成した *Common.asmdef* は他の asmdef を参照せず、逆に他3つの asmdef から参照される形になります。*Common.asmdef* 内のクラスが変更された場合、広範囲の asmdef に影響がでる形になります。

# おわり

UnityのAssembly definitionは簡単に定義出来て強力な効果を発揮するのでUnityを使うなら勉強しておいて損は無いはずです。実際私は asmdef のレイヤ定義の過程で依存性逆転の理解がかなりできました。

ちなみに、Dependencer/Dependenceeは造語です。reviewer/revieweeのように、
 
* Dependencer: 依存する人(利用側)
* Dependencee: 依存される側(利用される側)

を意図しています。

asmdefによるレイヤ分けに興味が出てきた方は是非こちらもご覧ください

 * [CleanArchitectureでasmdefをガチった話(Unity)](https://qiita.com/naninunenoy/items/07bc2088877318771565)