Unity高性能依赖注入框架Extenject(Zenject)-----如何进行高级绑定

Binding

Gameobject绑定(并设置Name和父物体)

针对于新创建的游戏对象的绑定(eg:FromComponentInNewPrefab FromNewComponentOnNewGameObject …),这里还额外提供了两种方法:
WithGameObjectName 完成绑定并对新创建的游戏物体命名。(笔者认为:类似于gameobject.name=“Foo1”)

//根据资源路径完成绑定,场景中新添加的游戏物体被命名为“Foo1”
Container.Bind<Foo>().FromComponentInNewPrefabResource("Some/Path/Foo").WithGameObjectName("Foo1");
Container.Bind<Foo>().FromNewComponentOnNewGameObject().WithGameObjectName("Foo1");

UnderTransformGroup(string) 设置新创建的游戏对象所在的Transform Group,这对于工厂模式很有用。因为工厂可能创建大量的Prefab副本,因此最好在Hierarchy中将它们自动分组在一起。

Container.BindFactory<Bullet, Bullet.Factory>()
    .FromComponentInNewPrefab(BulletPrefab)
    .UnderTransformGroup("Bullets");

Demo:

using Zenject;
public class FactorDemo : MonoInstaller
{
    public Food _food;
    public override void InstallBindings()
    {
        Container.BindFactory<Food, FoodFactory>()
            .FromComponentInNewPrefab(_food)
            .WithGameObjectName("水饺")
            .UnderTransformGroup("中国食品");
    }
    private void Start()
    {
        FoodFactory foodFactory = Container.Resolve<FoodFactory>();
        foodFactory.Create();
        foodFactory.Create();
        foodFactory.Create();
    }
    public class FoodFactory:PlaceholderFactory<Food>
    {
        
    }
}

在这里插入图片描述
UnderTransform(Transform) 设置父物体

Container.BindFactory<Bullet, Bullet.Factory>()
    .FromComponentInNewPrefab(BulletPrefab)
    .UnderTransform(BulletTransform);

UnderTransform(Method) 使用一个方法来提供Transform

Container.BindFactory<Foo, Foo.Factory>()
    .FromComponentInNewGameObject()
    .UnderTransform(GetParent);

Transform GetParent(InjectContext context)
{
    if (context.ObjectInstance is Component)
    {
        return ((Component)context.ObjectInstance).transform;
    }

    return null;
}
Optional Binding

(笔者观点:一旦声明为 [InjectOptional],就可以认为已经完成绑定,引用类型绑定为Null,值类型绑定为默认值(如int 默认为 0);如果此时通过代码进行人工绑定,则绑定值以代码绑定为准)
可以将一些依赖项声明为可选绑定,如下所示:

public class Bar
{
    public Bar( [InjectOptional] IFoo foo)
    {
       ...
    }
}
...

// 即使删除掉也不影响使用
Container.Bind<IFoo>().AsSingle();

同样,Identifier也可以使用:

public class Bar
{
    public Bar( [Inject(Optional = true, Id = "foo1")] IFoo foo)
    {
        ...
    }
}

如果在任何 Installers 中都没有绑定可选的依赖项,那么它将被注入为null。
如果依赖项是原始类型(例如int,float,struct),则将使用其默认值(例如,对于int 为0)进行注入。
您还可以使用标准C#方式分配一个明确的默认值,例如:

public class Bar
{
    public Bar(int foo = 5)
    {
        ...
    }
}
...
// 删除掉这行代码,5就会被使用
Container.BindInstance(1);

还要注意,以下这种情况下[InjectOptional]是不必要的,因为默认值已经隐含了它。或者,您可以将基本参数定义为可为空,并根据是否提供该参数执行逻辑,例如:

public class Bar
{
    int _foo;
   //int? 表示int 类型可以置为空
    public Bar([InjectOptional] int? foo)
    {
        if (foo == null)
        {
            // Use 5 if unspecified
            _foo = 5;
        }
        else
        {
            _foo = foo.Value;
        }
    }
}
...
// 如果执行下行代码,那么_foo=1,如果没有执行则_foo=5
Container.BindInstance(1);
条件绑定

在一些情况下,可以使用下面的语法在给不同的类注入依赖时进行限制:

//如果 Bar1 Bar2 中均有声明为IFoo的变量,这是注入的是不一样的
Container.Bind<IFoo>().To<Foo1>().AsSingle().WhenInjectedInto<Bar1>();
Container.Bind<IFoo>().To<Foo2>().AsSingle().WhenInjectedInto<Bar2>();

上述是一种简写形式,使用 “When” 的通用写法如下:

Container.Bind<IFoo>().To<Foo>().AsSingle().When(context => context.ObjectType == typeof(Bar));

InjectContext类(作为上面的context参数传递)包含以下信息,您可以在条件中使用该信息:

  • **Type ObjectType **:注入依赖的类(即我们将为该类注入依赖项),可使用这个选项为不同的类注入不同的内容。
  • object ObjectInstance
  • string Identifier
  • object ConcreteIdentifier
  • string MemberName
  • Type MemberType
  • InjectContext ParentContext
  • bool Optional 如果在要注入的字段上声明了[InjectOptional],则为True

本节将通过案例进行详细介绍:链接地址

List绑定

当Zenject找到同一类型的多个绑定时,他将形成一个列表。在下面的示例代码中,Bar将获得一个包含Foo1,Foo2和Foo3的新实例的列表:

// In an installer somewhere
Container.Bind<IFoo>().To<Foo1>().AsSingle();
Container.Bind<IFoo>().To<Foo2>().AsSingle();
Container.Bind<IFoo>().To<Foo3>().AsSingle();

...
public class Bar
{
    public Bar(List<IFoo> foos)
    {
    }
}

列表的顺序将与使用Bind方法添加它们的顺序相同。(当使用Sub_Container时有所不同,列表将首先由关联的Sub_Container排序,从最底层的Sub_Container中获取,然后是parent,然后是 grandparent)

全局绑定(Project Context)

一个游戏中必然包含多个场景,如何让某些依赖项在所有场景中都起作用呢?在Zenject中可以通过将 installers 添加到ProjectContext 中来实现这个目的。

第一步:创建一个ProjectContext的Prefab
点击 Edit -> Zenject -> Create Project Context 之后,在Resources文件夹中将会出现需要的预制体。
在这里插入图片描述
在这里插入图片描述
Create -> Zenject -> ProjectContext ,也可创建出Prefab
在这里插入图片描述
点击查看Prefab,就会发现其Inspector面板和Scene Context的面板几乎相同
在这里插入图片描述

最简单的配置方式就是通过拖拽直接添加 Installers ,除了Installers ,还可以将自己定义的Monobehaviours类直接拖拽到Prefab上。
第二步 运行程序
当启动任何包含了SceneContext的场景时,ProjectContext 会被优先初始化,在此处添加的所有 Installers 将被执行,并且您在其中添加的绑定将可用于项目中的所有场景。 ProjectContext游戏对象设置为DontDestroyOnLoad,因此在更改场景时不会将其销毁。
注:ProjectContext 只有在加载第一个具有SceneContext的场景是初始化一次,当中途切换场景,不会再次调用ProjectContext,但先前的绑定可以保留在新场景中。可以与Scene Installers 相同的方式在ProjectContext的 Installers 中声明继承了ITickable / IInitializable / IDisposable的对象,结果是IInitializable.Initialize在每次运行中仅被调用一次,IDisposable.Dispose仅在应用程序关闭时被调用。

原理剖析:为什么添加到全局 installer 中的所有绑定可注入单独场景中的类?
每个场景中的 Container 是 ProjectContext Container的 ”子级“。

ProjectContext是一个非常好的保存跨场景对象的位置。但是这也可能带来一些不利影响,例如,即使使用Zenject编写一些简单的测试场景,也会加载ProjectContext,这并不是我们想要的。为了解决这个问题,最好使用 Scene Parenting 功能,使用这个方法可以选择哪些场景可以继承公共绑定。

通过ProjectContext实例化的gameobject都默认位于其下方,如果希望希望实例化的对象出在hierarchy 根目录(仍标记为DontDestroyOnLoad),可取消ProjectContext 的Inspector面板中’Parent New Objects Under Context’ 的选定。
在这里插入图片描述

Identifiers(同一类型完成多个绑定)

在前面讲到的技术大多都是为一个类型绑定一个实例,绑定多个实例的情况被绑定成了List<>,那么我们可不可以一个类型具有多个不同的绑定,还不是List呢? 答案是肯定的,这就是使用Identifiers技术,下面将通过例子做详细讲解。
在下面的例子中,Bar1 类 中的 _foo字段 会被实例化为 Foo1 类型,而 Bar2 中的 _foo 字段会被会被实例化为 Foo2 类型。

Container.Bind<IFoo>().WithId("foo").To<Foo1>().AsSingle();
Container.Bind<IFoo>().To<Foo2>().AsSingle();

...

public class Bar1
{
    [Inject(Id = "foo")]
    IFoo _foo;
}

public class Bar2
{
    [Inject]
    IFoo _foo;
}

这项技术也可以通过构造函数注入和方法注入被使用:

public class Bar
{
    Foo _foo;

    public Bar(
        [Inject(Id = "foo")]
        Foo foo)
    {
    }
}

在很多实例中(可以参考上边的例子),ID都被设置为了string 类型,实际上你可以使用任意你喜欢的类型,有时候使用枚举类型可能会有意想不到的优势。(只要实现了Equals运算符,自定义类型也允许被使用)

enum Cameras
{
    Main,
    Player,
}

Container.Bind<Camera>().WithId(Cameras.Main).FromInstance(MyMainCamera);
Container.Bind<Camera>().WithId(Cameras.Player).FromInstance(MyPlayerCamera);
非泛型绑定(Non Generic bindings)

在一些案例中,编译时您可能不知道要绑定的具体类型,在这些情况下,可以使用Bind方法的重载,该方法采用System.Type值而不是确切的参数。

// These two lines will result in the same behaviour
Container.Bind(typeof(Foo));
Container.Bind<Foo>();

还可以多个参数同时传递:

Container.Bind(typeof(Foo), typeof(Bar), typeof(Qux)).AsSingle();

// The above line is equivalent to these three:
Container.Bind<Foo>().AsSingle();
Container.Bind<Bar>().AsSingle();
Container.Bind<Qux>().AsSingle();

To方法也同理:

Container.Bind<IFoo>().To(typeof(Foo), typeof(Bar)).AsSingle();

// The above line is equivalent to these two:
Container.Bind<IFoo>().To<Foo>().AsSingle();
Container.Bind<IFoo>().To<Bar>().AsSingle();

还可以两个一起搞一个自动组合:

Container.Bind(typeof(IFoo), typeof(IBar)).To(typeof(Foo), typeof(Bar)).AsSingle();

// The above line is equivalent to these:
Container.Bind<IFoo>().To<Foo>().AsSingle();
Container.Bind<IFoo>().To<Bar>().AsSingle();
Container.Bind<IBar>().To<Foo>().AsSingle();
Container.Bind<IBar>().To<Bar>().AsSingle();

当一个类实现多个接口时:

Container.Bind(typeof(ITickable), typeof(IInitializable), typeof(IDisposable)).To<Foo>().AsSingle();

实现的接口太多不想一个一个写时:

Container.BindInterfacesTo<Foo>().AsSingle();

这里提供的API还是很充足的,使用的时候怎么舒服怎么来,看大家的使用习惯。

批量绑定(Convention Based Binding)

灵魂发问:在什么情景下使用这个技术呢?答案就是你需要大量绑定而自己又懒得敲代码的时候。

  1. 如果想通过一个命名约定来决定如何绑定到Container。(如根据前缀、后缀或者更复杂的正则表达式来确定绑定规则)。
  2. 如果你想通过自定义attributes 来确定绑定规则。
  3. 如果你想自动绑定一个程序集/命名空间中实现了某个接口的所有类。

使用 “convention over configuration” 可以定义一个绑定框架,其他人也可以复用你的框架,快速帅气的完成任务而不是苦逼的一个个添加绑定。这是Ruby on Rails,ASP.NET MVC等框架所遵循的哲学。当然有利有弊,需要你权衡使用。

这和上面的 Non Generic bindings 绑定有些类似,区别在于不需要使用Bind()和To() 方法并提供绑定列表。而是使用Fluent API。举个栗子:要将IFoo绑定到在整个代码库中实现它的所有类

Container.Bind<IFoo>().To(x => x.AllTypes().DerivingFrom<IFoo>());

同样Band方法中也可以使用Fluent API,来描述你要绑定的类。也可以Bind()和To() 都用,随意组合。

先写一个 Fluent API 的完整形式在这,后边对其进行一一解释,最后通过例子教你怎么用。

x.InitialList().Conditional().AssemblySources()

InitialList :这是你需要绑定的初始列表,通过下列枚举的指令过滤出你想绑定的类或接口。这些指令看名字都可以才出来进行了怎样的过滤。

  • AllTypes 所有的类型
  • AllNonAbstractClasses 所有的非Abstract类
  • AllAbstractClasses 所有的Abstract类
  • AllInterfaces 所有的接口
  • AllClasses 所有的Class

**Conditional ** :过滤 InitialList 中类和接口的过滤器(也可以理解为筛选器,根据匹配条件筛选出希望完成绑定的类)。

  • DerivingFrom 保留从T派生的匹配类型
  • DerivingFromOrEqual 保留从T派生的匹配类型或等于T的类型
  • WithPrefix(value) 保留以value开头的类型,前缀匹配
  • WithSuffix(value) 保留以value结尾的类型,后缀匹配
  • WithAttribute 保留被[T]特性标明的类型
  • WithoutAttribute 保留没有被[T]特性标明的类型
  • WithAttributeWhere (predicate) 保留被[T]特性标注且通过自定义predicate匹配的类型,这很有用,因此可以使用赋予attribute 的数据来创建绑定
  • InNamespace(value) 保留命名空间等于value的类型
  • InNamespaces(value1, value2, etc.) 保留命名空间等于value1, value2, etc.的类型
  • MatchingRegex(pattern) 保留名称匹配正则表达式pattern的类型
  • Where(predicate) 通过predicate进行匹配

**AssemblySources ** :在程序集层面予以限定

  • FromAllAssemblies 在所有已加载的程序集中查找类型,默认值。
  • FromAssemblyContaining 在类型T所在的所有程序集中查找
  • FromAssembliesContaining(type1, type2, …) 在包含给定类型的所有程序集中查找
  • FromThisAssembly 在调用此方法的程序集中查找
  • FromAssembly(assembly) 在给定的程序集中查找类型
  • FromAssemblies(assembly1, assembly2, …) 在给定的程序集中查找类型
  • FromAssembliesWhere(predicate) 在所有与给定predicate匹配的程序集中查找
Demos:

可以在绑定中将下面的条件任意组合进行匹配查找。下面的示例中没有指定程序集,因此默认使用 **FromAllAssemblies ** 在所有已加载的程序集中查找类型。 (绑定的前提要满足,两个毫无关系的类肯定无法绑定)

  1. 将IFoo绑定到在整个代码库中实现它的所有类:
Container.Bind<IFoo>().To(x => x.AllTypes().DerivingFrom<IFoo>());
//等同于
Container.Bind<IFoo>().To(x => x.AllNonAbstractTypes());
//使用AllNonAbstractTypes而不是AllTypes 是为了避免将 IFoo 绑定 到自身
  1. 绑定”MyGame.Foos“命名空间内,所有的是实现了”IFoo“接口的类
Container.Bind<IFoo>().To(x => x.AllTypes().DerivingFrom<IFoo>().InNamespace("MyGame.Foos"));
  1. 绑定具有"Controller" 前缀的类
Container.Bind<IController>().To(x => x.AllNonAbstractTypes().WithSuffix("Controller"));
//等价于
Container.Bind<IController>().To(x => x.AllNonAbstractTypes().MatchingRegex("Controller$"));
  1. 绑定所有具有"Widget后缀的类到Foo
Container.Bind<object>().To(x => x.AllNonAbstractTypes().WithPrefix("Widget")).WhenInjectedInto<Foo>();
  1. 将所有接口绑定到实现了它们的非抽象类上,并且这些类都在"MyGame.Things"命名空间
Container.Bind(x => x.AllInterfaces())
    .To(x => x.AllNonAbstractClasses().InNamespace("MyGame.Things"));
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值