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

前言

新手使用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地址
Zenject 中文社区,搭建中
讨论群:518658723
欢迎喜欢使用DI的朋友加入群,有自愿报名活动!
本人代码功底浅,大神看到就当路过,希望来点评!


呀哈哈,有能力的朋友可以扫下面的打赏二维码↓↓↓
打赏

你可能感兴趣的:(Zenject框架)