C# 依赖注入和开发生产率

C# 依赖注入和开发生产率,c#,dependency-injection,architecture,ninject,structuremap,C#,Dependency Injection,Architecture,Ninject,Structuremap,摘要 在过去的几个月里,我一直在用API抽象和实体/组件/脚本系统编程一个轻量级、基于C#的游戏引擎。它的整体思想是通过提供类似于Unity引擎的架构,简化XNA、SlimDX等游戏的开发过程 设计挑战 正如大多数游戏开发人员所知,在代码中需要访问许多不同的服务。许多开发人员求助于使用全局静态实例,例如渲染管理器(或编写器)、场景、图形设备(DX)、记录器、输入状态、视口、窗口等。有一些全局静态实例/单例的替代方法。一种是通过构造函数或构造函数/属性依赖项注入(DI)为每个类提供其需要访问的类的

摘要

在过去的几个月里,我一直在用API抽象和实体/组件/脚本系统编程一个轻量级、基于C#的游戏引擎。它的整体思想是通过提供类似于Unity引擎的架构,简化XNA、SlimDX等游戏的开发过程

设计挑战

正如大多数游戏开发人员所知,在代码中需要访问许多不同的服务。许多开发人员求助于使用全局静态实例,例如渲染管理器(或编写器)、场景、图形设备(DX)、记录器、输入状态、视口、窗口等。有一些全局静态实例/单例的替代方法。一种是通过构造函数或构造函数/属性依赖项注入(DI)为每个类提供其需要访问的类的实例,另一种是使用全局服务定位器,如StructureMap的ObjectFactory,其中服务定位器通常配置为IoC容器

依赖注入

我选择走DI的路有很多原因。最明显的一个是可测试性,通过对接口编程,并通过构造函数将每个类的所有依赖项提供给它们,这些类很容易测试,因为测试容器可以实例化所需的服务或它们的模拟,并将其输入到要测试的每个类中。信不信由你,执行DI/IoC的另一个原因是为了提高代码的可读性。不再需要实例化所有不同服务和手动实例化引用所需服务的类的庞大初始化过程。配置内核(NInject)/注册表(StructureMap)可以方便地为引擎/游戏提供一个单一的配置点,在这里选择和配置服务实现

我的问题

  • 我经常觉得我是为了接口而创建接口的
  • 我的生产力急剧下降,因为我所做的只是担心如何用DI的方式来做事情,而不是快速简单的全局静态方式
  • 在某些情况下,例如在运行时实例化新实体时,需要访问IoC容器/内核来创建实例。这就产生了对IoC容器本身的依赖(SM中的ObjectFactory,Ninject中内核的实例),这实际上违背了首先使用IoC容器的理由。如何解决这个问题?抽象工厂浮现在脑海中,但这只会使代码更加复杂
  • 根据服务需求,某些类的构造函数可能会变得非常大,这将使该类在其他未使用IoC的环境中完全无用

基本上,执行DI/IoC会大大降低我的工作效率,在某些情况下会使代码和体系结构更加复杂。因此,我不确定这是一条我应该走的道路,还是放弃,用老式的方式做事。我不是在寻找一个单一的答案来说明我应该做什么或不应该做什么,而是讨论从长远来看,使用DI是否值得,而不是使用全局静态/单一方式,在处理DI时,我忽略了可能的优点和缺点,以及上面列出的问题的可能解决方案

你应该回到传统的方式吗? 简言之,我的答案是否定的。基于你提到的所有原因,DI有许多好处

我经常觉得我是为了接口而创建接口的

如果您这样做,您可能违反了

根据服务需求,某些类的构造函数可以 非常大,这将使该类在其他方面完全无用 未使用国际奥委会的情况

如果您的类构造函数太大、太复杂,这是向您表明您违反了一个非常重要的其他原则的最佳方式: . 在本例中,是时候将代码提取并重构为不同的类了,建议的依赖项数量大约为4个

为了进行DI,您不必有接口,DI只是将依赖项放入对象的方式。创建接口可能是为测试目的替换依赖项所必需的方法。 除非依赖项的对象是:

  • 容易隔离
  • 不与外部子系统(文件系统)通信 等)
  • 您可以将依赖项创建为一个抽象类,或者任何要替换为虚拟方法的类。然而,接口确实创建了依赖项的最佳解耦合方式

    在某些情况下,例如在运行时实例化新实体时 需要访问IoC容器/内核才能创建实例。 这将创建对IoC容器本身(ObjectFactory)的依赖关系 在SM中,是Ninject中内核的一个实例),它实际上 反对首先使用一个的理由。这怎么可能 断然的?抽象的工厂浮现在脑海中,但这只是进一步 使代码复杂化

    至于对IOC容器的依赖关系,在客户端类中永远不应该对它有依赖关系。 他们也不必这么做

    为了首先正确地使用依赖项注入,需要理解依赖项注入的概念。这是唯一应该引用容器的地方。此时,将构建整个对象图。一旦你明白了这一点,你就会意识到你的客户永远不需要容器。因为每个客户机只是注入了它的依赖项

    您还可以遵循许多其他创作模式,以简化构建: 假设您要构造一个具有许多依赖项的对象,如下所示:

    new SomeBusinessObject(
        new SomethingChangedNotificationService(new EmailErrorHandler()),
        new EmailErrorHandler(),
        new MyDao(new EmailErrorHandler()));
    
     SomeBusinessObject bo = SomeBusinessObjectFactory.Create();
    
    您可以创建一个具体的工厂,知道如何建造:

    public static class SomeBusinessObjectFactory
    {
        public static SomeBusinessObject Create()
        {
            return new SomeBusinessObject(
                new SomethingChangedNotificationService(new EmailErrorHandler()),
                new EmailErrorHandler(),
                new MyDao(new EmailErrorHandler()));
        }
    }
    
    然后像这样使用它:

    new SomeBusinessObject(
        new SomethingChangedNotificationService(new EmailErrorHandler()),
        new EmailErrorHandler(),
        new MyDao(new EmailErrorHandler()));
    
     SomeBusinessObject bo = SomeBusinessObjectFactory.Create();
    
    您还可以使用poor mans di并创建一个
    public interface IDataAccessFactory
    {
        TDao Create<TDao>();
    }
    
    public class ConcreteDataAccessFactory : IDataAccessFactory
    {
        private readonly IocContainer _Container;
    
        public ConcreteDataAccessFactory(IocContainer container)
        {
            this._Container = container;
        }
    
        public TDao Create<TDao>()
        {
            return (TDao)Activator.CreateInstance(typeof(TDao),
                this._Container.Resolve<Dependency1>(), 
                this._Container.Resolve<Dependency2>())
        }
    }
    
    public class SomeOtherBusinessObject
    {
        private IDataAccessFactory _DataAccessFactory;
    
        public SomeOtherBusinessObject(
            IDataAccessFactory dataAccessFactory,
            INotificationService notifcationService,
            IErrorHandler errorHandler)
        {
            this._DataAccessFactory = dataAccessFactory;
        }
    
        public void DoSomething()
        {
            for (int i = 0; i < 10; i++)
            {
                using (var dao = this._DataAccessFactory.Create<MyDao>())
                {
                    // work with dao
                    // Console.WriteLine(
                    //     "Working with dao: " + dao.GetHashCode().ToString());
                }
            }
        }
    }