Unity高性能依赖注入框架Extenject(Zenject)----详解DIContainer

编程过程中,可能会遇到直接使用DIContainer的情况。例如:编写自定义工厂时,可能需要直接调用DiContainer.Instantiate方法。实例化第三方库的类时,可能需要调用DiContainer.Inject来进行手动注入。

使用者可以将DiContainer注入到任何类中,但是通过注入DiContainer的方式编程并不被推荐,90%都可以找到一种更好的解决方案(自定义工厂是一个例外情况,但即如此,使用时也最好使用将一个工厂注入自定义工厂的方案)。再次强调,依赖注入的最好使用方式是仅在“ composition root layer”中引用DiContainer,“ composition root layer”包括了自定义工厂和Installers,但遇到例外情况也可以酌情考虑。

DiContainer.Instantiate

在开始介绍之前我们必须明确,下面所讲到到的实例化方法对于自定义工厂可能很有用,但是大多数情况下都是可以避免直接引用DiContainer的,改用普通的Factory。
当直接实例化对象时,可以使用DiContainer或者使用IInstantiator(DiContainer继承自IInstantiator),IInstantiator存在的意义是因为在自定义工厂中通常只对实例化操作感兴趣,不需要Bind,Resolve等方法。

  1. Instantiate :根据泛型T提供的类调用 New运算符来创建实例,并进行注入。该方法不可以用于Components/MonoBehaviours(个人认为这些根本就new不出来,所以用了也白用),但是可以用于ScriptableObject 派生的类(在这种情况下,Zenject自动调用ScriptableObject.CreateInstance)。
Foo foo = Container.Instantiate<Foo>();

还可以传递参数:

Foo foo = Container.Instantiate<Foo>(new object[] { "foo", 5 });

非泛型版本:

Foo foo = (Foo)Container.Instantiate(typeof(Foo));
Foo foo = (Foo)Container.Instantiate(typeof(Foo), new object[] { "foo", 5 });
  1. InstantiatePrefab :实例化指定的Prefab,并对Prefab上的脚本进行注入。
GameObject gameObject = Container.InstantiatePrefab(MyPrefab);

等价于:

var gameObject = GameObject.Instantiate(MyPrefab);
DiContainer.Inject(gameObject);

传递的“MyPrefab”参数可以是一个Gameobject,也可以是Prefab上的Component。这和Unity自带的GameObject.Instantiate类似,所以你也可以这么写:

GameObject gameObject = Container.InstantiatePrefab(MyPrefab, MyParentTransform);
  1. InstantiatePrefabResource :和上面讲解的 InstantiatePrefab 类似,传递的参数是Prefab的资源路径。
GameObject gameObject = Container.InstantiatePrefabResource("path/to/myprefab");

等价于:

GameObject gameObject = Container.InstantiatePrefab(Resources.Load("path/to/myprefab"));
  1. InstantiatePrefabForComponent :实例化Prefab,进行注入,返回值是指定的组件T(T组件必须存在与Prefab的层级结构中)
var foo = Container.InstantiatePrefabForComponent<Foo>(FooPrefab)

和上述的 InstantiatePrefab 方法不同,我们还可以给指定的Componnent传递参数:

var foo = Container.InstantiatePrefabForComponent<Foo>(FooPrefab, new object[] { "asdf", 6.0f })
  1. InstantiatePrefabResourceForComponent :和InstantiatePrefabForComponent类似,传递的参数是Prefab的资源路径。
var foo = Container.InstantiatePrefabResourceForComponent<Foo>("path/to/fooprefab")
var foo = Container.InstantiatePrefabResourceForComponent<Foo>("path/to/fooprefab", new object[] { "asdf", 6.0f })
  1. InstantiateComponent:将T组件添加到指定的gameobject上,并完成注入。
var foo = Container.InstantiateComponent<Foo>(gameObject);
var foo = Container.InstantiateComponent<Foo>(gameObject, new object[] { "asdf", 6.0f });

等价于:

 T component =gameObject.AddComponent<T>();
 Container.inject(component);
  1. InstantiateComponentOnNewGameObject:创建一个新的gameobject,实例化一个T类型组件添加到gameobject上。
var foo = Container.InstantiateComponentOnNewGameObject<Foo>();
var foo = Container.InstantiateComponentOnNewGameObject<Foo>(new object[] { "zxcv" });

说白了就是先 new Gameobject(),然后调用 DiContainer.InstantiateComponent。

  1. InstantiateScriptableObjectResource :看名字基本也可以猜功能,通过资源路径加载的方式实例化ScriptableObject,如果想创建一个新对象,直接使用DiContainer.Instantiate就可以。
var foo = Container.InstantiateScriptableObjectResource<Foo>("path/to/fooscriptableobject")
var foo = Container.InstantiateScriptableObjectResource<Foo>("path/to/fooscriptableobject", new object[] { "asdf", 6.0f })

DiContainer.Bind

入门
高级绑定
代码示例

DiContainer.Resolve

  1. **DiContainer.Resolve **: 获取指定类型的实例对象,这和绑定时设置的Scope有关,返回值可能是new 出来的的新实例,也可能返回一个已经存在的实例。
Container.Bind<Foo>().AsSingle();
...
var foo = Container.Resolve<Foo>();

如果在Container中解析不到Foo类(如果你压根就没绑定,或绑定的和解析的不是同一个Container),会会抛出错误,解析时使用TryResolve,Try字头的方法大家应该都了解有什么优势;如果你绑定了很多,可以使用ResolveAll 方法。

  1. DiContainer.ResolveId 和上面一样,多加一个identifier
Container.Bind<Foo>().WithId("foo1").AsSingle();
...
var foo = Container.ResolveId<Foo>("foo1");
  1. DiContainer.TryResolve :和DiContainer.Resolve类似,区别在于解析不出来不报错,而是返回一个null。
var foo = Container.TryResolve<Foo>();

if (foo != null)
{
    ...
}
  1. DiContainer.TryResolveId :理解 2/3 的说明就理解了。
  2. DiContainer.ResolveAll :和DiContainer.Resolve类似,返回值时List。
List<Foo> foos = Container.ResolveAll<Foo>();
  1. DiContainer.ResolveIdAll :参看 5和2 的解释。
  2. DiContainer.ResolveType :在Install阶段调用会很安全,不会实例化任何内容。如果找到0个匹配或多个匹配会报错。可以用来安全校验。
if (Container.ResolveType<IFoo>() == typeof(Foo))
{
    ...
}
  1. DiContainer.ResolveTypeAll :和ResolveType类似,区别在于匹配多个类型,返回一个列表。

DiContainer.Inject

  1. DiContainer.Inject :注入给定的实例
Container.Inject(foo);

带参数注入:

Container.Inject(foo, new object[] { "asdf", 6 });

注入优先级:成员变量(Fields),属性(Properties),方法注入(Inject methods)
2. DiContainer.InjectGameObject :指定的gameobject上所有Mono组件都进行注入(前提是需要注入,没有[Inject]的想注入也没有啊)

Container.InjectGameObject(gameObject);

将按其依赖顺序注入所有组件。 如果将A注入到B中并且将B注入到C中,那么将首先注入A,然后是B,然后是C。与它们在层次结构中位置无关,只与依赖顺序有关。
3. DiContainer.InjectGameObjectForComponent :和InjectGameObject 类似,区别在于返回值是注入完成的组件。

var foo = Container.InjectGameObjectForComponent<Foo>(gameObject);

这个方法还支持给指定的组件传递参数:

var foo = Container.InjectGameObjectForComponent<Foo>(gameObject, new object[] { "asdf", 5.1f });

这里假定的是只有一个匹配项,要是同时包含了多个匹配的脚本,报错 报错 报错,重要的事情说三遍。

DiContainer.QueueForInject

当object graph构造完成后,DiContainer.QueueForInject 将会给需要注入的实例排个队。有的实例不是通过Zenject创建出来的,启动的时候他们已经实例化出来了,这个时候你想注入就需要这样写:

var foo = new Foo();
...
Container.Bind<Foo>().FromInstance(foo);

等价于:

Container.BindInstance(foo);

但是,使用了FromInstance 并不意味着指定的实例就被注入完成了。调用这个方法可以快速注入:

Container.BindInstance(foo);
Container.Inject(foo);

这时候问题又来了,这样注入并不是我们想要的。因为我们希望在Install阶段永远不会实例化或注入,因为要注入的对象没准还没完成绑定,很可能一不注意就报错了(自己能注意依赖顺序的当我没说),报错了你还不好找到哪里错了,你说气不气。这时候就该QueueForInject发挥作用了

Container.BindInstance(foo);
Container.QueueForInject(foo);

这么一写,foo对象就会在object graph 构造完成后立刻调用(就是在结束Install阶段后立刻注入),舒服了。使用QueueForInject的话Zenject就会保证你的实例注入顺序永远是对的,举个栗子:

class A
{
    [Inject]
    public void Init()
    {
        ...
    }
}

class B
{
    [Inject]
    public void Init(A a)
    {
        ...
    }
}

按照下面的方式一些,管他谁依赖谁,让Zenject自己处理去吧。

var a = new A();
var b = new B();

Container.BindInstance(a);
Container.BindInstance(b);

Container.QueueForInject(a);
Container.QueueForInject(b);

这样我们就可以保证A在B注入之前完成注入,与添加到容器的顺序无关。 在Monobehaviours中发生的注入,从来不需要我们考虑注入顺序就是因为使用了QueueForInject方法。

DiContainer Unbind / Rebind

有绑定需求就会有解绑需求和更改绑定需求,但是解绑和更改绑定并不是推荐的方式,能不用就不用(直接删掉重写很难吗?干嘛非得把错误记录下来)。不过特殊的情况下可能用得到,就写一下:

  1. Unbind :通过指定的 类型和ID 解绑
Container.Bind<IFoo>().To<Foo>();
// 下边这句代码的意思就是,上句话无效,是废话,我撤回
Container.Unbind<IFoo>();
  1. Rebind :将原来的绑定用一个新的替换掉,相当与先解绑,再绑一个新的
Container.Bind<IFoo>().To<Foo>();
Container.Rebind<IFoo>().To<Bar>();

Other DiContainer methods

  1. DiContainer.ParentContainers :给定Container容器的父Container,例如:SceneContext的DiContainer的ParentContainer通常是ProjectContext 的Container(如果使用了Scene Parenting功能,那么SceneContext的DiContainer的父DiContainer可能是别的SceneContext的DiContainer)绕口令好玩不。
  2. DiContainer.IsValidating 如果该DIContainer正在进行验证,那么返回True,使用的可能性很小,只有在验证步骤添加逻辑处理才可能用的到。
  3. DiContainer.CreateSubContainer :创建一个container作为当前container的sub_container,自定义工厂中如果创建具有复杂依赖关系的对象可能用得到。(推荐使用FromSubContainerResolve方法)
var subContainer = Container.CreateSubContainer();

subContainer.Bind<Foo>();
subContainer.Bind<Bar>();
subContainer.Bind<Qux>();

var foo = subContainer.Resolve<Foo>();
  1. DiContainer.HasBinding :检查给定的 type/id 是否已经添加绑定。如果你不想重复添加绑定,最好绑定前做个一安全检验。
if (!Container.HasBinding<IFoo>())
{
    Container.Bind<IFoo>().To<Foo>().AsSingle();
}
  1. DiContainer.GetDependencyContracts :以列表形式返回指定类型的所有依赖类型,如果你想静态分析项目或者想自己生成依赖关系图,可能用得上。
发布了13 篇原创文章 · 获赞 15 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/weixin_43405845/article/details/104415593