What is Init(args)?
Init(args) is a dependency injection framework for Unity, built with a keen focus on ensuring all aspects of the toolset blend in seamlessly with the native editor experience.
What problems does Init(args) solve?
The Unity Editor's scene-based workflow is a powerful tool, and the ability to drag-and-drop references to fields in the Inspector is already a great foundation for injecting dependencies to Objects! As such, the aim with Init(args) has not been to reinvent the wheel on this front, but rather to fix various shortcomings it has and unlock its full potential.
In vanilla Unity, all components are responsible for finding references to the objects they depend on. One way to accomplish this is by adding serialized fields, which enables users to assign references using the Inspector. As cool as this is, it has its limitations, and can't be used in every situation:
Can't assign Objects to interface fields.
Can't assign Objects across scenes or prefabs.
Dragging the same managers to components again and again is laborious.
To swap a manager to a different one you have to manually update references in all components.
Can't assign dynamic values, which makes working with things like localized texts, addressable assets and randomized values more difficult.
Due to these limitations, components often have to combine the usage of serialized fields with other means of resolving dependencies: singletons, GetComponent, GetComponentInChildren, FindObjectOfType, FindWithTag, static methods... and so on. This hodgepodge way of fetching the dependencies of components also has its own problems:
It can add a lot of unnecessary noise to your components' code.
Dependencies can be hidden anywhere within the code.
Dependencies are usually hardwired to specific classes.
Dependencies are located using hardwired search methods.
And even where you are able to piece together a relatively dignified component using some combination of these methods, it will still most likely be virtually impossible to write any unit tests for it!
Init(args) can resolve all of these issues for you.
Keeping It Simple
With Init(args) you won't have to spend large amounts of time learning about perplexing concepts, like bindings, containers, decorators or contexts... in fact, you can use just the Inspector to fulfill most of your dependency injection needs! - but with so much more flexibility than ever before.
And what about when you do want to initialize objects with arguments in code? Here as well, you can make use of all the commands you are already familiar with, like AddComponent and Instantiate - just with the added ability to pass in some arguments.
This small but significant change means that your components are no longer closed islands, black boxes with hidden dependencies; when you instantiate a component with its dependencies, you can feel assured that it will work. This also means that writing unit testing for your components becomes so frictionless and easy, that creating them can become downright addictive!
Pure Dependency Injection
The main focus of Init(args) is on enabling pure dependency injection, both with the Inspector for scene objects and prefabs, as well as in code. What this means is that reflection is used as sparingly as possible, and instead generic methods and interfaces are used to provide a strongly typed pathways through which dependencies can be delivered.
This approach has various benefits, such as solid performance compared to reflection-based solutions, and user errors being caught as early as possible at compile-time - and this fail-fast design is also in effect on the Editor side, with warnings being logged to the Console about any missing arguments detected in your scenes in edit mode.
The use of pure dependency injection can also help a lot with the legibility of your project; dependencies between objects are not obfuscated, and you can easily follow the trail of dependency breadcrumbs both in your IDE and in the Editor - a vital ability to have when debugging your application.
Just a Pinch of Magic Sprinkled On Top...
In addition to Init(args) offering all the tools you'll ever need for pure dependency injection in Unity, there's also a powerful system that can be used to, as if by magic, create shared instances of select classes and automatically deliver them to clients everywhere (or, if you so choose, limited to certain scenes or transform hierarchies).
These shared instances are called "Services", and you can create a new one from any class, just by adding the [Service] attribute to it. You can think of services like an enhanced version of singletons.
Even easier to create (just add an attribute).
Automatically delivered to all client components.
Services can easily make use of other services (without execution order issues).
Service arguments can be overridden for individual components when needed.
Supports loading from Addressables, Resources folde
什么是 Init(args)?
Init(args) 是 Unity 的依赖注入框架,其构建重点是确保工具集的各个方面与本机编辑器体验无缝融合。
Init(args) 解决了什么问题?
Unity 编辑器基于场景的工作流程是一个强大的工具,并且在检查器中拖放对字段的引用的能力已经为向对象注入依赖项奠定了良好的基础! 因此,Init(args) 的目标并不是在这方面重新发明轮子,而是修复其存在的各种缺点并释放其全部潜力。
在普通 Unity 中,所有组件都负责查找对其所依赖的对象的引用。 实现此目的的一种方法是添加序列化字段,这使用户能够使用检查器分配引用。 尽管这很酷,但它也有其局限性,并且不能在所有情况下使用:
无法将对象分配给接口字段。
无法跨场景或预制件分配对象。
一次又一次地将相同的管理人员拖到组件中是很费力的。
要将管理器更换为其他管理器,您必须手动更新所有组件中的引用。
无法分配动态值,这使得处理本地化文本、可寻址资源和随机值等内容变得更加困难。
由于这些限制,组件通常必须将序列化字段的使用与其他解决依赖关系的方法结合起来:单例、GetComponent、GetComponentInChildren、FindObjectOfType、FindWithTag、静态方法...等等。 这种获取组件依赖关系的大杂烩方式也有其自身的问题:
它会给组件代码添加很多不必要的噪音。
依赖关系可以隐藏在代码中的任何位置。
依赖关系通常硬连接到特定的类。
使用硬连线搜索方法来定位依赖关系。
即使您能够使用这些方法的某种组合来拼凑出一个相对有尊严的组件,但实际上仍然不可能为其编写任何单元测试!
Init(args) 可以为您解决所有这些问题。
保持简单
使用 Init(args),您不必花费大量时间学习令人困惑的概念,例如绑定、容器、装饰器或上下文……事实上,您可以仅使用检查器来满足大部分依赖项注入需求! - 但比以往任何时候都更加灵活。
当您确实想用代码中的参数初始化对象时该怎么办? 在这里,您也可以使用您已经熟悉的所有命令,例如 AddComponent 和 Instantiate - 只需添加传递某些参数的功能即可。
这个微小但意义重大的变化意味着您的组件不再是封闭的孤岛、具有隐藏依赖项的黑匣子; 当您实例化一个组件及其依赖项时,您可以放心它会工作。 这也意味着为组件编写单元测试变得如此无摩擦和容易,以至于创建它们可能会让人完全上瘾!
纯依赖注入
Init(args) 的主要重点是通过场景对象和预制件的检查器以及代码来启用纯依赖项注入。 这意味着尽可能少地使用反射,而是使用通用方法和接口来提供强类型路径,通过该路径可以传递依赖项。
这种方法具有多种优点,例如与基于反射的解决方案相比具有稳定的性能,以及在编译时尽早捕获用户错误 - 并且这种快速失败设计在编辑器端也有效,并记录警告 向控制台报告在编辑模式下场景中检测到的任何缺失参数。
使用纯依赖注入也对提高项目的易读性有很大帮助; 对象之间的依赖关系不会被混淆,并且您可以轻松地在 IDE 和编辑器中跟踪依赖关系面包屑的踪迹 - 这是调试应用程序时至关重要的能力。
只需在上面撒上一点魔法...
除了 Init(args) 提供在 Unity 中进行纯依赖注入所需的所有工具之外,还有一个强大的系统,可以像魔法一样创建选定类的共享实例并自动将它们传递给 客户端无处不在(或者,如果您选择的话,仅限于某些场景或转换层次结构)。
这些共享实例称为“服务”,您可以从任何类创建一个新实例,只需向其添加 [Service] 属性即可。 您可以将服务视为单例的增强版本。
更容易创建(只需添加一个属性)。
自动交付给所有客户端组件。
服务可以轻松地使用其他服务(没有执行顺序问题)。
需要时可以为各个组件覆盖服务参数。
支持从Addressables、Resources文件夹加载
|