BindInterfacesTo 和 BindInterfacesAndSelfTo
如果你使用了ITickable, IInitializable, 和 IDisposable 接口,代码会变成这样:
Container.Bind(typeof(Foo), typeof(IInitializable), typeof(IDisposable)).To<Logger>().AsSingle();
这有点冗长,同时也不够理想,因为假如我稍后决定Foo不再使用Tick()或者Dispose(),那我就必须同时修改安装器。
一个更好的想法可能是只以下面的方式使用接口:
Container.Bind(new[] { typeof(Foo) }.Concat(typeof(Foo).GetInterfaces())).To<Foo>().AsSingle();
这种方式足够有用并且Zenject自定义了一个这种方式的绑定方法。上面的代码相当于:
Container.BindInterfacesAndSelfTo<Foo>().AsSingle();
现在,我们可以添加或者删除Foo的接口,而安装器不用修改。
在某些情况下,你可能只需要绑定的接口,并保持Foo对其他类隐藏。在这种情况下,您可以使用BindInterfacesTo方法:
Container.BindInterfacesTo<Foo>().AsSingle()
在该例中,相当于:
Container.Bind(typeof(IInitializable), typeof(IDisposable)).To<Foo>().AsSingle();
使用Unity的检视面板配置设置
将大部分代码编写为普通C#类而不是MonoBehaviour的一个后果是,您无法使用检查面板中配置数据。但是,您可以通过在Zenject中使用下面的方式来实现此功能:
public class Foo : ITickable
{
readonly Settings _settings;
public Foo(Settings settings)
{
_settings = settings;
}
public void Tick()
{
Debug.Log("Speed: " + _settings.Speed);
}
[Serializable]
public class Settings
{
public float Speed;
}
}
然后,在安装器中:
public class TestInstaller : MonoInstaller<TestInstaller>
{
public Foo.Settings FooSettings;
public override void InstallBindings()
{
Container.BindInstance(FooSettings);
Container.BindInterfacesTo<Foo>().AsSingle();
}
}
或者,等价的:
public class TestInstaller : MonoInstaller<TestInstaller>
{
public Foo.Settings FooSettings;
public override void InstallBindings()
{
Container.BindInterfacesTo<Foo>().AsSingle().WithArguments(FooSettings);
}
}
现在,如果我们运行场景,我们可以在检视面板中实时调整Foo类的speed值。
另一种(可以说是更好的)方法是使用ScriptableObjectInstaller
而不是MonoInstaller
,ScriptableObjectInstaller
具有额外的优势,您可以在运行时更改您的设置,并在停止运行时自动保存这些更改。详情请见“Scriptable Object 安装器”章节。
对象图验证
概览
使用DI框架设置绑定时的工作流程通常如下:
- 在代码中添加一些绑定
- 执行你的应用程序
- 观察一堆与DI相关的异常
- 修改绑定以解决问题
- 重复
这适用于小型项目,但随着项目复杂性的增加,这通常是一个单调乏味的过程。如果应用程序的启动时间特别糟糕,或者异常仅发生在运行时各个点的工厂,则问题会变得更严重。使用一些工具来分析您的对象图并告诉您所有缺少的绑定的确切位置,而无需启动整个应用程序试是非常好的。
你可以通过Edit -> Zenject -> Validate Current Scene
进行验证,或者在打开要验证场景的情况下按Shift+Alt+V
进行验证,这将会验证当前场景的所有安装器。然后遍历对象图并验证是否可以找到所有绑定(而实际上不会实例化任何绑定)。换句话说,它执行正常启动过程的“干运行”。在引擎盖下,这可以通过在容器中存储虚拟对象来代替实际实例化类来实现。
或者,您可以执行菜单项Edit -> Zenject -> Validate Then Run
或只是按下CTRL+SHIFT+R
。这将验证您打开的场景,然后如果验证成功,它将开始运行。验证通常非常快,所以相较于点击“运行”按钮这可能是一个更好的选择,特别是当你的游戏启动时间很长的时候。
请注意,这还将包括工厂和内存池,这非常有用,因为这些错误可能在启动后的某个时间才会被捕获。
有几点需要注意:
- 没有执行实际的逻辑代码 - 只调用安装绑定。这意味着如果除了绑定命令之外的安装器中有逻辑,那么这些逻辑也会执行,并且在运行验证时可能会导致问题(如果该逻辑要求容器返回实际值)
- null值将会被注入实际实例化的依赖项中,例如安装器(绑定内容的关键字)
您可能希望在验证模式下注入一些类。在这种情况下,您可以用它们标记它们[ZenjectAllowDuringValidation]
。
另请注意,某些验证行为可在zenjectsettings中配置
自定义验证
如果要添加自己的验证逻辑,只需将一个类继承IValidatable即可完成此操作。执行此操作后,只要您的类在某个安装器中绑定,它将在验证期间实例化,然后Validate()方法将被调用。但请注意,它所具有的任何依赖项将被注入为null(除非标记为[ZenjectAllowDuringValidation]属性)。
如果您希望验证失败,您可以在Validate方法内部抛出异常,或者只是将信息记录到控制台。自定义validatable中出现的一个常见问题是实例化无法验证的类型。通过在验证期间实例化它们,它将确保可以解决所有依赖关系。
例如,如果您创建一个直接使用Container.Instantiate<Foo>()
实例化类型的自定义工厂,则Foo不会被验证,因此在运行时之前您不会发现它是否缺少某些依赖项。但是,您可以通过让工厂实现IValidatable
然后在Validate()
方法内部调用Container.Instantiate<Foo>()
来解决此问题。