简介
一些情况下,使用多个容器在同一个应用里面非常有帮助;
例如,如果你创建一个文字处理,你可能需要多个子容器为每个标签代表一个隔离的文档;
这种处理方式,你可能使用子容器绑定一堆类成AsSingle()
,
并且这些如果全部是单例容器可以容易相互引用;然后你可能为每个文档实例多个子容器,
每个子容器拥有唯一的实例,所有的类在每个指定文档处理
另外一个实例你可能设计一个开放世界的太空船游戏,你可能想每个太空船拥有自己的容器,
包含所有的类实例承担运行指定的飞船
这也是ProjectContext
的实际绑定工作方式;整个项目有一个容器,当unity场景启动,
容器内部的每个SceneContext
在ProjectContext
容器之下创建;
所有你添加到场景里面的MonoInstaller
绑定到你的SceneContext
容器里面;这将允许
你场景的引用自动获得ProjectContext
绑定注入,因为子容器自动关联他们父容器所有绑定
一个通用的设计模式是我们喜欢联系子容器使用外观模式(门面模式);
这个模式通常抽象隔离引用关系组,因此可以用作更高级模块当通过其他模块作为基础编码;
这里是相关的,因为你会经常定义子容器到你的应用里面,并且非常有帮助,
定义外观类通常关联这个子容器的全部;因此,应用这些到上面的飞船游戏里面,
你可能需要一个SpaceshipFacade
类代表非常高等级太空船操作,
比如开启引擎、收到伤害、飞到目的地等等;SpaceshipFacade
类可以代理特殊的处理,
所有的这些部分请求相关的 单一职责 引用在子容器里面存在
子容器也可以非常有效的方式组织大部分的代码到区分合并的模块;
你可以在高层管理引用,使用模块之间的引用替代代码;
没有子容器,你的基础代码越写越多,所有的存在作为单例在同一个层级,加剧不方便,
一旦每个类可以依赖每个其他的类,仅仅是添加构建参数;
替代成长的关系类到他们自己的子容器并强制其他需求类通过外观类来交互,
这样更容易管理和理解整个部分代码引用;
你可以想象,每个外观类为整个子容器作为一个具体类型,他们的具体实现在子容器里面不可见,
你可以有多个不同低层类拥有他们自己的单一职责;
结果将会低耦合代码,这样将来重构、维护、测试、阅读更简单
Hello World Example For Sub-Containers/Facade
public class Greeter
{
readonly string _message;
public Greeter(string message)
{
_message = message;
}
public void DisplayGreeting()
{
Debug.Log(_message);
}
}
public class GameController : IInitializable
{
readonly Greeter _greeter;
public GameController(Greeter greeter)
{
_greeter = greeter;
}
public void Initialize()
{
_greeter.DisplayGreeting();
}
}
public class TestInstaller : MonoInstaller
{
public override void InstallBindings()
{
Container.BindInterfacesTo<GameController>().AsSingle();
Container.Bind<Greeter>().FromSubContainerResolve().ByMethod(InstallGreeter).AsSingle();
}
void InstallGreeter(DiContainer subContainer)
{
subContainer.Bind<Greeter>().AsSingle();
subContainer.BindInstance("Hello world!");
}
}
重要的事情是理解这里任何绑定我们添加到InstallGreeter
方法将只会在子容器里面是可见的;
仅有的异常是外观类一旦绑定到父容器,使用FromSubContainerResolve
来绑定;
这个例子里面,Hello Wrold只对Greeter类可见
注意以下事项:
- 你应该总是添加绑定语句,无论类是否指定
FromSubContainerResolve
,在子容器里面的方法,
如果你没有添加本体的绑定信息到你的子容器里面,在验证的时候你会得到异常 - 不要使用上面示例的方法,通常更好是使用
ByInstaller
替代ByMethod
;这是因为当你使用ByMethod
,
容易发生引用容器替代子容器,同样,通过使用ByInstaller
你可以给安装器自己传递参数 - 子容器也可以使用在类似动态生产外观,使用
BindFactory
和FromSubContainerResolve
- 这种方法有一些弊端,当使用MonoBehaviour’s或者使用IInitializable/ITicakable/IDisposable接口
Creating Sub-Containers on GameObject’s by using Game Object Context
上面的示例不能够很好的为MonoBehaviour类运行的一个问题;
没有什么阻止我们添加MonoBehaviour绑定,例如
FromConponentInNewPrefab,FromNewComponentOnNewGameObject,等等;
直到我们的的ByInstaller/ByMethod子容器 - 然而这些将会转换这些新游戏对象添加到层级视图的根节点,
因此我们将必须手动跟踪这些对象的生命周期,
通过调用在他们上面GameObject.Destroy,当他们的外观类销毁了;
同样,没有方式保证场景中存在的GameObject在启动的存在,但在子容器里面退出;
还有,使用ByInstaller和ByMethod像上面那样不支持使用这些接口,比如IInitializable/
ITickable/IDisposable在子容器里面;这些问题可以通过使用GameObject Context来解决
开放世界太空船例子:
- 创建一个新场景
- 添加以下文件到你的项目里面
using Zenject;
using UnityEngine;
public class Ship : MonoBehaviour
{
ShipHealthHandler _healthHandler;
[Inject]
public void Construct(ShipHealthHandler healthHandler)
{
_healthHandler = healthHandler;
}
public void TakeDamage(float damage)
{
_healthHandler.TakeDamage(damage);
}
}
using UnityEngine;
using Zenject;
public class GameRunner : ITickable
{
readonly Ship _ship;
public GameRunner(Ship ship)
{
_ship = ship;
}
public void Tick()
{
if (Input.GetKeyDown(KeyCode.Space))
{
_ship.TakeDamage(10);
}
}
}
using Zenject;
public class GameInstaller : MonoInstaller
{
public override void InstallBindings()
{
Container.BindInterfacesTo<GameRunner>().AsSingle();
}
}
using Zenject;
using UnityEngine;
public class ShipHealthHandler : MonoBehaviour
{
float _health = 100;
public void OnGUI()
{
GUI.Label(new Rect(Screen.width / 2, Screen.height / 2, 200, 100), "Health: " + _health);
}
public void TakeDamage(float damage)
{
_health -= damage;
}
}
using UnityEngine;
using System.Collections;
public class ShipInputHandler : MonoBehaviour
{
[SerializeField]
float _speed = 2;
public void Update()
{
if (Input.GetKey(KeyCode.UpArrow))
{
this