/AsmdefDependencyPattern

More strictly layer definitions using Assembly definition Files.

Primary LanguageC#

AsmdefDependencyPattern

A loosely coupled code is preferred.

The introduction of an anti-corruption layer and the Dependency Inversion Principle (DIP) protect your code from external library changes.

In Unity, you can separate assembly space by Assembly definition. It allows for more powerful layer separation

Main has IDependencee object to do something. Unity life cycle will call Main.Start() and DoSomething().

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

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

Main does not know implemention of IDependencee. It means changing Dependency implemention do not effect Main.

In addition, Dependencer.asmdef does not refer DependenceeImplement.asmdef so you can not even construct Dependencee object in Main.

Dependencer.asmdef refers to DependenceeAbstruct.asmdef. This is anti-corruption layer assembly space and contains only interfaces that define the behavior.

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

Of course, DependenceeImplement.asmdef refers to DependenceeAbstruct.asmdef to implement interfaces.

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

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

Wait, how and who provide Dependencee object to Main?

The answer is Dependency Injection(DI).

Zenject is DI framework wroks on Unity. If IDependencee is requested, set it to return an instance of Dependencee.

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

DependenceIntermediary.asmdef refers to both DependenceeAbstruct.asmdef and DependenceeImplement.asmdef to resolove dependency.

And Zenject.SceneContext will resolove dependency between IDependencee and Dependencee in scene, before Main.Awake() called.


Do you need a class to traverse these layers?

In order to refer from these assembly spaces, you need to create a new assembly space and define class there.

Common.asmdef does not refer to an assembly, but it is referred to by 3 assemblies.

The Assembly definition help you strictly separate the layers within your Unity project.

see also: https://github.com/naninunenoy/UnityCleanArchitectureExample

Auther

@naninunenoy