前言

新手使用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>>()
  • 引出的弊端
    1.如果拥有多个构造函数工厂的时候,Zenject会分不清楚,游戏少见,App常见
    2.不要使用非内嵌类的工厂,当参数过多累赘
  • 注意事项
    1.验证动态创建的对象特别有用
    2.从mono动态实例化的对象,不要使用Inject来初始化逻辑
    3.工厂对象要严格绑定构造方法

绑定语法

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();

字段简单解释

  • ContractType = 需要返回Create方法的实体对象
  • PlaceholderFactoryType = PlaceholderFactory的子类
  • WithFactoryArguments = 给PlaceholderFactory传递参数,WithArguments给实体传递参数
  • Scope = 由于工厂需要经常用到,默认使用频率是AsCached替代AsTransient
    注:其他的绑定方法和非工厂的绑定方法类似

抽象工厂

如果你想使用接口来替代实体类的创建方式,使用抽象工厂来实现,代码示例:

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>();}}
}

自定义工厂,工厂方法IFactory

如果只有在程序运行之后我才能决定我需要创建什么类型,
或者我需要指定请求构建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

如果你不想定义额外的工厂类,你可以直接注入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的时候,验证将不会被运行

IVaildatable接口实现

如果你需要使用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地址


10.Unity Zenject高级编程(使用工厂动态创建物体)相关推荐

  1. Python面试常用的高级用法,怎么动态创建类?

    元类是Python当中的 高级用法 ,如果你之前从来没见过这个术语或者是没听说过这个概念,这是非常正常的,因为一方面它的 使用频率不高 ,另外一方面就是它相对 不太容易理解 .以至于很多Python开 ...

  2. C# Activator的使用(类工厂动态创建类的实例)

    包含特定的方法,用以在本地或从远程创建对象类型,或获取对现有远程对象的引用.此类不能被继承--c# Activator c#在类工厂中动态创建类的实例 1. Activator.CreateInsta ...

  3. 【DOM编程艺术】动态创建标记(签)---创建和插入节点

    window.οnlοad=function(){var para=document.createElement('p');var info= 'nodeName:';info += para.nod ...

  4. linux如何实现网络高级编程,嵌入式Linux网络编程之:网络高级编程-嵌入式系统-与非网...

    10.3  网络高级编程 在实际情况中,人们往往遇到多个客户端连接服务器端的情况.由于之前介绍的如connet().recv()和send()等都是阻塞性函数,如果资源没有准备好,则调用该函数的进程将 ...

  5. gd动态曲线 php_PHP 高级编程之多线程

    PHP 高级编程之多线程 目录 1. 多线程环境安装 1.1. PHP 5.5.9 1.2. 安装 pthreads 扩展 2. Thread 3. Worker 与 Stackable 4. 互斥锁 ...

  6. c#高级编程第11版 pdf网盘_c#高级编程_c#高级编程 目录 微盘_c#高级编程第10版 pdf...

    c#高级编程 C#高级编程(第9版)-C# 5.0 & .NET 4.5.1是由.NET专家的梦幻组合编写,包含开发人员使用C#所需的所有内容.C#是编写.NET应用程序的一种语言,本书适合于 ...

  7. 【嵌入式】C语言高级编程-内联函数(10)

    00. 目录 文章目录 00. 目录 01. 属性声明 02. 内联函数概述 03. 内联函数与宏 04. 编译器对内联函数的处理 05. static修饰内联函数 06. 附录 01. 属性声明 a ...

  8. C#高级编程——C#扩展方法+接口,定义统一的搜索接口,基于Unity(三)——图文详解加源码

    C#高级编程--C#扩展方法+接口,定义统一的搜索接口,基于Unity(三)--图文详解加源码 前言

  9. C# 6 与 .NET Core 1.0 高级编程 - 41 ASP.NET MVC(上)

    译文,个人原创,转载请注明出处(C# 6 与 .NET Core 1.0 高级编程 - 41 ASP.NET MVC(上)),不对的地方欢迎指出与交流. 章节出自<Professional C# ...

最新文章

  1. CSS之布局(盒子的水平布局)
  2. java static null,我们可以在Java中使用null对象调用静态方法吗?如果是这样,怎么样?...
  3. python对XML的解析
  4. 如何使用CDR智能填充工具
  5. 为什么这个SQL Server DBA学习PowerShell--SQL任务
  6. 在左表或右表的连接字段上建立索引对左、内连接的查询效率的优化情况分析
  7. maven创建java项目_使用maven命令行创建java项目
  8. 目标检测性能评价指标mAP、Precision、Recall、IoU
  9. 最短路径Dijkstra算法(邻接矩阵)
  10. Lecture Notes: Macros
  11. (Pr)Premiere Pro 2022 软件下载+Pr安装教程
  12. iOS 人脸识别功能
  13. python中空格怎么打_191012 python3关于空格打印、赋值、+=符号的小坑
  14. 编写第一个操作WORD文档的应用程序
  15. 双模控制器很耗电_电动车双电双核控制系统是在说什么?这个部件的发展一日千里!...
  16. 高通开发系列 - System之FOTA和DFOTA升级记录
  17. TensorFlow北大公开课学习笔记8-复现vgg16并实现图片识别
  18. java文档注释添加url链接和class跳转链接
  19. mplus 软件_Mplus基础系列教程(三)
  20. 英语文章关于计算机的,关于计算机的雅思英语作文范文

热门文章

  1. 50Hz双T陷波滤波器(带阻滤波器)
  2. Facebook等群聊天工具要加密
  3. 计算机微机原理pdf,《微机原理与接口技术》作业.pdf
  4. 基于PyQt5实现访问Web应用程序或网页
  5. android开发自动拍照,android使用camera2实现隐藏式的相机自动拍照
  6. 稳压电源技术指标,摘录
  7. linux开源同步软件,开源备份也安全 六大国外免费Linux工具
  8. Mp3tag for Mac(音频标签编辑器)
  9. Anaconda的安装教程
  10. 【连连支付】PHP第三方连连支付对接