新手使用Zenject框架经常会遇到一个头痛的问题,怎么样在游戏启动以后动态创建新物体;假如你做一个游戏需要生成很多的敌人(Enemies),然后你将会去构建新的敌人实类,并且要确保这些敌人实类会注入到框架里面,建议使用工厂来处理
DI框架重要的部分是储备使用容器,严格的遵循"Composition Root Layer"组合跟节点层;DiContainer容器类包括自身的自动引用,因此将无法阻止你忽略这个规则并且注入容器任意你想要的类,代码示例:
public class Enemy
{
DiContainer Container;
public Enemy(DiContainer container)
{
Container = container;
}
public void Update()
{
...
var player = Container.Resolve<Player>();
WalkTowards(player.Position);
...
etc.
}
}
然而,上面的代码是一个反面例子,可以正常运行,你可以使用Container访问游戏里面其他的类,如果你这样做的话,严格意义上来说,这不是DI框架的魅力所在;
当然,你可以这样编写代码:
public class Enemy
{
Player _player;
public Enemy(Player player)
{
_player = player;
}
public void Update()
{
...
WalkTowards(_player.Position);
...
}
}
但现在,每一个创建新Enemy的地方需要补充一个Player的实例,这样违背了软件设计原则的开闭原则,当使用工厂来处理以后,Zenject会自动补充另外需要的实类
public class Player
{
}
public class Enemy
{
readonly Player _player;
public Enemy(Player player)
{
_player = player;
}
public class Factory : PlaceholderFactory<Enemy>
{
}
}
public class EnemySpawner : ITickable
{
readonly Enemy.Factory _enemyFactory;
public EnemySpawner(Enemy.Factory enemyFactory)
{
_enemyFactory = enemyFactory;
}
public void Tick()
{
if (ShouldSpawnNewEnemy())
{
var enemy = _enemyFactory.Create();
// ...
}
}
}
public class TestInstaller : MonoInstaller
{
public override void InstallBindings()
{
Container.BindInterfacesTo<EnemySpawner>().AsSingle();
Container.Bind<Player>().AsSingle();
Container.BindFactory<Enemy, Enemy.Factory>();
}
}
通过使用Ememy.Factory来替代new Enemy,所有的敌人引用类,类如Player类会自动填充;
我们也可以给工厂添加参数,代码示例:
public class Enemy
{
readonly Player _player;
readonly float _speed;
public Enemy(float speed, Player player)
{
_player = player;
_speed = speed;
}
public class Factory : PlaceholderFactory<float, Enemy>
{
}
}
public class EnemySpawner : ITickable
{
readonly Enemy.Factory _enemyFactory;
public EnemySpawner(Enemy.Factory enemyFactory)
{
_enemyFactory = enemyFactory;
}
public void Tick()
{
if (ShouldSpawnNewEnemy())
{
var newSpeed = Random.Range(MIN_ENEMY_SPEED, MAX_ENEMY_SPEED);
var enemy = _enemyFactory.Create(newSpeed);
// ...
}
}
}
public class TestInstaller : MonoInstaller
{
public override void InstallBindings()
{
Container.BindInterfacesTo<EnemySpawner>().AsSingle();
Container.Bind<Player>().AsSingle();
Container.BindFactory<float, Enemy, Enemy.Factory>();
}
}
供给Enemy构造器的动态参数通过额外的生成参数到PlaceholderFactory<>,Enemy.Factory的基类;PlaceholderFactory<>使用Create方法来使用提供的参数类型;
Enemy.Factory总是有意让其实现为空,因为这是直接从内置PlaceholderFactory<>类(占位工厂),它将会使用DIContainer来构建一个新实类Enemy来处理生成的内容;被称作占位工厂是因为不包含实际的对象创建过程
这种工厂在安装器里面声明方式和非工厂引用的声明方式相同,例如Mono的Enemy,安装方式:
public class Enemy : MonoBehaviour
{
Player _player;
// Note that we can't use a constructor anymore since we are a MonoBehaviour now
[Inject]
public void Construct(Player player)
{
_player = player;
}
public class Factory : PlaceholderFactory<Enemy>
{
}
}
public class TestInstaller : MonoInstaller
{
public GameObject EnemyPrefab;
public override void InstallBindings()
{
Container.BindInterfacesTo<EnemySpawner>().AsSingle();
Container.Bind<Player>().AsSingle();
Container.BindFactory<Enemy, Enemy.Factory>().FromComponentInNewPrefab(EnemyPrefab);
}
}
类似的,你可以通过FromMethod的拓展方法来实例化动态对象,见Binding方式
使用FromSubContainerResolve可以明确你拥有多个引用的时候,你可以理解这种行为像外观模式
没有要求Enemy.Factory类是Enemy的内嵌类,但我们发现这是一种非常友好的编码方式;如果不使用Enemy的内嵌工厂,安装器的绑定方式:
Container.BindFactory<Enemy, PlaceholderFactory<Enemy>>()
Container.BindFactory<ContractType, PlaceholderFactoryType>()
.WithId(Identifier)
.WithFactoryArguments(Factory Arguments)
.To<ResultType>()
.FromConstructionMethod()
.AsScope()
.WithArguments(Arguments)
.OnInstantiated(InstantiatedCallback)
.When(Condition)
.NonLazy()
.(Copy|Move)Into(All|Direct)SubContainers();
字段简单解释
如果你想使用接口来替代实体类的创建方式,使用抽象工厂来实现,代码示例:
public interface IPathFindingStrategy
{
...
}
public class AStarPathFindingStrategy : IPathFindingStrategy
{
...
}
public class RandomPathFindingStrategy : IPathFindingStrategy
{
...
}
public class PathFindingStrategyFactory : PlaceholderFactory<IPathFindingStrategy>
{
}
public class GameController : IInitializable
{
PathFindingStrategyFactory _strategyFactory;
IPathFindingStrategy _strategy;
public GameController(PathFindingStrategyFactory strategyFactory)
{
_strategyFactory = strategyFactory;
}
public void Initialize()
{
_strategy = _strategyFactory.Create();
// ...
}
}
public class GameInstaller : MonoInstaller
{
public bool UseAStar;
public override void InstallBindings()
{
Container.BindInterfacesTo<GameController>().AsSingle();
if (UseAStar)
{
Container.BindFactory<IPathFindingStrategy, PathFindingStrategyFactory>().To<AStarPathFindingStrategy>();
}
else
{
Container.BindFactory<IPathFindingStrategy, PathFindingStrategyFactory>().To<RandomPathFindingStrategy>();
}
}
}
如果只有在程序运行之后我才能决定我需要创建什么类型,或者我需要指定请求构建Enemy实例没有被任何的构造方法覆盖的时候,你需要自定义工厂来管理对象的动态创建过程,代码示例:
public enum Difficulties
{
Easy,
Hard,
}
public interface IEnemy
{
}
public class EnemyFactory : PlaceholderFactory<IEnemy>
{
}
public class Demon : IEnemy
{
}
public class Dog : IEnemy
{
}
public class DifficultyManager
{
public Difficulties Difficulty
{
get;
set;
}
}
public class CustomEnemyFactory : IFactory<IEnemy>
{
DiContainer _container;
DifficultyManager _difficultyManager;
public CustomEnemyFactory(DiContainer container, DifficultyManager difficultyManager)
{
_container = container;
_difficultyManager = difficultyManager;
}
public IEnemy Create()
{
if (_difficultyManager.Difficulty == Difficulties.Hard)
{
return _container.Instantiate<Demon>();
}
return _container.Instantiate<Dog>();
}
}
public class GameController : IInitializable
{
readonly EnemyFactory _enemyFactory;
public GameController(EnemyFactory enemyFactory)
{
_enemyFactory = enemyFactory;
}
public void Initialize()
{
var enemy = _enemyFactory.Create();
// ...
}
}
public class TestInstaller : MonoInstaller
{
public override void InstallBindings()
{
Container.BindInterfacesTo<GameController>().AsSingle();
Container.Bind<DifficultyManager>().AsSingle();
Container.BindFactory<IEnemy, EnemyFactory>().FromFactory<CustomEnemyFactory>();
}
}
继承自IFactory< IEnemy>并且使用FromFactory方法来绑定声明;
上面的代码你也可以直接使用new Dog()和new Demon()来替代使用DiContainer,但Dog和Demon的成员不会注入
注意,FromFactory< CustomEnemyFactory>相对于FromIFactory(b=>b.To< CustomEnemyFactory>().AsCached())轻度;
使用FromIFactory替代FromFactory更强化,因为自定义的工厂类型可以使用构造器创建,包括FromSubContainerResolve、
FromInstance、FromComponentInNewPrefab,etc;
public class CustomEnemyFactory : IFactory<IEnemy>
{
Dog.Factory _dogFactory;
Demon.Factory _demonFactory;
DifficultyManager _difficultyManager;
public CustomEnemyFactory(
DifficultyManager difficultyManager, Dog.Factory dogFactory, Demon.Factory demonFactory)
{
_dogFactory = dogFactory;
_demonFactory = demonFactory;
_difficultyManager = difficultyManager;
}
public IEnemy Create()
{
if (_difficultyManager.Difficulty == Difficulties.Hard)
{
return _demonFactory.Create();
}
return _dogFactory.Create();
}
}
如果你不想定义额外的工厂类,你可以直接注入IFactory<>包含任何使用的类,然后使用BindFactory方法来构建方法:
public class GameController : IInitializable
{
IFactory<IPathFindingStrategy> _strategyFactory;
IPathFindingStrategy _strategy;
public GameController(IFactory<IPathFindingStrategy> strategyFactory)
{
_strategyFactory = strategyFactory;
}
public void Initialize()
{
_strategy = _strategyFactory.Create();
// ...
}
}
public class GameInstaller : MonoInstaller
{
public bool UseAStar;
public override void InstallBindings()
{
Container.BindInterfacesTo<GameController>().AsSingle();
if (UseAStar)
{
Container.BindIFactory<IPathFindingStrategy>().To<AStarPathFindingStrategy>();
}
else
{
Container.BindIFactory<IPathFindingStrategy>().To<RandomPathFindingStrategy>();
}
}
}
在某些情况下,你可能想要避免直接从工厂类联系,你定义基类或者接口来替代;
通过使用BindFactoryCustomInterface方法替代BindFactory,类如:
public interface IMyFooFactory : IFactory<Foo>
{
}
public class Foo
{
public class Factory : PlaceholderFactory<Foo>, IMyFooFactory
{
}
}
public class Runner : IInitializable
{
readonly IMyFooFactory _fooFactory;
public Runner(IMyFooFactory fooFactory)
{
_fooFactory = fooFactory;
}
public void Initialize()
{
var foo = _fooFactory.Create();
// ...
}
}
public class FooInstaller : MonoInstaller<FooInstaller>
{
public override void InstallBindings()
{
Container.BindFactoryCustomInterface<Foo, Foo.Factory, IMyFooFactory>();
}
}
当你需要调用Create方法创建新游戏组件物体,你可以直接调用DiContainer。InstantiatePrefabForComponent,但将会违法DI容器的组合跟节点层原则,工厂和安装器都违反了这个原则,最好是写一个自定义工厂来替代:
public class Foo
{
public class Factory : PlaceholderFactory<UnityEngine.Object, Foo>
{
}
}
public class FooFactory : IFactory<UnityEngine.Object, Foo>
{
readonly DiContainer _container;
public FooFactory(DiContainer container)
{
_container = container;
}
public Foo Create(UnityEngine.Object prefab)
{
return _container.InstantiatePrefabForComponent<Foo>(prefab);
}
}
public override void InstallBindings()
{
Container.BindFactory<UnityEngine.Object, Foo, Foo.Factory>().FromFactory<FooFactory>();
}
然而,这种自定义的工厂太过于常见,有一个工具类来简化工厂创建,PrefabFactory,简化代码:
public class Foo
{
public class Factory : PlaceholderFactory<UnityEngine.Object, Foo>
{
}
}
public class TestInstaller : MonoInstaller<TestInstaller>
{
public GameObject Prefab;
public override void InstallBindings()
{
Container.BindFactory<UnityEngine.Object, Foo, Foo.Factory>().FromFactory<PrefabFactory<Foo>>();
}
}
一个类似的帮助类,只需要传入string,从资源文件夹加载
public class Foo
{
public class Factory : PlaceholderFactory<string, Foo>
{
}
}
public class TestInstaller : MonoInstaller<TestInstaller>
{
public GameObject Prefab;
public override void InstallBindings()
{
Container.BindFactory<string, Foo, Foo.Factory>().FromFactory<PrefabResourceFactory<Foo>>();
}
}
注意当我们使用PrefabResources或者PrefabResourceFactory的时候,验证将不会被运行
如果你需要使用DiContainer的方法直接实例化,但你仍然需要验证动态创建的对象图表,你仍然可以继承自IValidatable接口,代码示例:
public class CustomEnemyFactory : IFactory<IEnemy>, IValidatable
{
DiContainer _container;
DifficultyManager _difficultyManager;
public CustomEnemyFactory(DiContainer container, DifficultyManager difficultyManager)
{
_container = container;
_difficultyManager = difficultyManager;
}
public IEnemy Create()
{
if (_difficultyManager.Difficulty == Difficulties.Hard)
{
return _container.Instantiate<Demon>();
}
return _container.Instantiate<Dog>();
}
public void Validate()
{
_container.Instantiate<Dog>();
_container.Instantiate<Demon>();
}
}
public class TestInstaller : MonoInstaller
{
public override void InstallBindings()
{
Container.BindFactory<IEnemy, EnemyFactory>().FromFactory<CustomEnemyFactory>();
}
}
注意不需要绑定IValidatable接口到我们的工厂,简单的继承接口,重写Validate()即可
Zenject Github地址
Zenject 中文社区,搭建中
讨论群:518658723
欢迎喜欢使用DI的朋友加入群,有自愿报名活动!
本人代码功底浅,大神看到就当路过,希望来点评!
呀哈哈,有能力的朋友可以扫下面的打赏二维码↓↓↓