依赖注入和发展生产力 [英] Dependency Injection and development productivity

查看:201
本文介绍了依赖注入和发展生产力的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

摘要

在过去的几个月里,我一直在一个重量轻编程,基于C#的游戏引擎与API抽象和实体/组件/脚本系统。它的整体思路是缓解游戏开发过程中的XNA,SlimDX并且这样,通过提供类似于Unity引擎的架构。

设计挑战

由于大多数游戏开发商知道,有很多不同的服务的你需要在你的code访问。许多开发人员求助于使用的例如全球静态实例渲染经理(或作曲),场景,GraphicsDevice的(DX),记录器,输入状态,视窗,窗等。有一些替代方法,以全局静态实例/单身。一种是给每个类,它需要访问类的实例,无论是通过构造函数或构造函数/属性依赖注入(DI),另一种是使用一个全球性的服务定位器,像StructureMap的ObjectFactory的该服务定位器通常被配置为IoC容器。

依赖关系注入

我选择去的DI方式的原因有很多。最明显的一个是可测性,通过编程对接口,并通过构造函数提供给他们的每一个类的所有依赖关系,这些类很容易测试,因为测试容器可以实例化所需的服务,或者他们的嘲笑,并馈入每类进行测试。另一个原因做DI /国际奥委会,不管你信不信,增加code的可读性。实例化所有不同的服务和手动到所需服务的引用实例化类没有更多的巨大的初始化过程。配置内核(NInject)/注册表(StructureMap)方便地提供了配置为发动机/游戏,其中,服务实现拾取和配置的单个点。

我的问题


  • 我常常觉得我的缘故接口创建接口

  • 我的生产率已经大幅下降,因为我要做的就是担心如何做事的DI-的方式,而不是快速和简单的全局静态的方式。

  • 在一些情况下,例如在运行时实例化新的实体时,需要访问IoC容器/内核创建实例。这将创建IoC容器本身的依赖关系的(在ObjectFactory的SM,在Ninject内核的实例)的,这确实违背了原因首先使用一个。这又如何解决?摘要工厂浮现在脑海中,但只是在code进一步复杂化。

  • 根据服务要求,某些类'的构造可以得到非常大的,这将使得类在,如果不使用一个IoC其他情况下完全没用。

基本上做的DI / IoC的急剧减慢我的生产率和在一些情况下,code和体系结构进一步复杂化。因此,我不确定是否是我应该遵循,或干脆放弃,做事情的老式方法的路径。我不是在寻找一个答案说什么我应该或不应该做的,但如果在使用DI的讨论是值得的,而不是使用全局静态/单身的方式从长远来看,可能的利弊我已经忽视,我的问题可能的解决方案上面列出,与DI的时候。


解决方案

如果你回到老式的方法是什么?
我在简短的答案是否定的。 DI对你提到的所有原因,许多好处。


  

我常常觉得我的缘故接口创建接口


如果你这样做,你可能会违反
重复使用抽象原则(RAP)


  

根据不同的服务要求,某些类'的构造可以得到
  非常大,这将使得该类完全没用其他
  上下文,其中,如果不使用一个IoC


如果你的类的构造函数过于庞大和复杂,这是给你的最佳途径,你违反了一个非常重要的原则,其他:
单Reponsibility原理的。在这种情况下,它是时间提取和重构code入不同的类,建议的依赖性的数目大约是4

为了做到DI你不必有一个接口,DI就是你得到你的依赖到你的对象的方式。创建界面可能是一个需要方法能够替代依赖用于测试目的。
除非依赖关系的目的是:


  1. 易于分离

  2. 不跟外部子系统(文件系统
    等)

您可以创建你的依赖作为一个抽象类,或者你想替代方法是虚拟的任何类。但是接口就创建一个依赖的最好的去耦合方式。


  

在一些情况下,例如实例上运行新的实体的时候,有
  需要访问IoC容器/内核创建实例。
  这将创建IoC容器本身的依赖关系(的ObjectFactory
  在SM,在Ninject内核的实例),从而真正去
  针对在第一个地方使用的原因之一。这是怎么回事
  解决?摘要工厂浮现在脑海中,但只是进一步
  复杂的code。


至于依赖于IOC容器,你不应该在你的客户端类的依赖它。
并且它们不必

在为第一次使用依赖注入适当的是要了解成分根的概念。这是你的容器应参考的地方。在这一点上你的整个对象图构造。一旦你明白这一点,你会发现,你永远不需要客户端的容器。由于每个客户端只得到它的依赖注入。

有你也可以按照使施工更方便许多其他创建模式:
说你要构造一个对象有许多依赖这样的:

 新SomeBusinessObject(
    新SomethingChangedNotificationService(新EmailErrorHandler()),
    新EmailErrorHandler(),
    新MyDao(新EmailErrorHandler()));

您可以创建一个具体的工厂,它知道如何构建这样的:

 公共静态类SomeBusinessObjectFactory
 {
        公共静态SomeBusinessObject的Create()
        {
            返回新SomeBusinessObject(
                    新SomethingChangedNotificationService(新EmailErrorHandler()),
                    新EmailErrorHandler(),
                    新MyDao(新EmailErrorHandler()));
        }
 }

,然后用它是这样的:

  SomeBusinessObject博= SomeBusinessObjectFactory.Create();

您也可以使用迪芒差,创建一个构造函数,在所有不带任何参数:

 公共SomeBusinessObject()
     {
         VAR的ErrorHandler =新EmailErrorHandler();
         VAR道=新MyDao(的ErrorHandler);
         VAR notificationService =新SomethingChangedNotificationService(的ErrorHandler);
         初始化(notificationService,的ErrorHandler,DAO);
     }
     保护无效初始化(
        INotificationService notifcationService,
        IErrorHandler的ErrorHandler,
        MyDao DAO)
     {
        this._NotificationService = notifcationService;
        this._ErrorHandler =的ErrorHandler;
        this._Dao =道;
     }

然后,它只是看起来像以前的工作:

  SomeBusinessObject博=新SomeBusinessObject();

用穷人的DI

当你有一个很好的默认实现被认为是不好当您的默认实现在外部第三方库,但不那么糟糕。

那么显然有所有的DI容器,对象建设者和其他模式。

因此​​,所有你需要的是思考你的对象了良好的创建模式的。你的对象本身不应该关心如何建立依赖关系,其实这使得他们更加复杂,使它们混合料2类型的逻辑。所以,我不敢相信使用DI应该有生​​产力的损失。

有一些特殊的情况下,你的对象不能只是得到注入到它的单个实例。其中寿命一般较短,在即时要求的实例。在这种情况下,你应该注入厂到对象作为依赖:

 公共接口IDataAccessFactory
{
    TDao创建< TDao>();
}

正如你可以看到这个版本是通用的,因为它可以使用IoC容器来创建不同类型的(请注意,虽然IoC容器仍是不可见的,我的客户)。

 公共类ConcreteDataAccessFactory:IDataAccessFactory
{
     IocContainer _container;
     公共ConcreteDataAccessFactory(IocContainer容器)
     {
         this._Container =容器;
     }     公共TDao创建< TDao>()
     {
            回报(TDao)Activator.CreateInstance(typeof运算(TDao),this._Container.Resolve< D​​ependency1>(),this._Container.Resolve< D​​ependency2>())
     }
   }

注意我用的催化剂,即使我有一个IoC容器,要注意的是,工厂需要构造对象的新实例,而不是只是假设容器将提供一个新的实例作为对象可能用不同的登记这是非常重要的寿命(辛格尔顿,ThreadLocal的,等等)。但是这取决于你使用的是一些能够产生这些工厂为哪些容器。然而,如果你有一定的对象与瞬态终生注册,您可以简单地解决这个问题。

编辑:添加类抽象工厂的依赖:

 公共类SomeOtherBusinessObject
 {
    私人IDataAccessFactory _DataAccessFactory;
    公共SomeOtherBusinessObject(
        IDataAccessFactory dataAccessFactory,
        INotificationService notifcationService,
        IErrorHandler的ErrorHandler)
    {
        this._DataAccessFactory = dataAccessFactory;
    }
    公共无效DoSomething的()
    {
        的for(int i = 0;我小于10;我++)
        {
            使用(VAR DAO = this._DataAccessFactory.Create< MyDao>())
            {
                //与DAO工作
                // Console.WriteLine(与DAO工作:+ dao.GetHash code()的ToString());
            }
        }
    }
 }


  

基本上是做DI / IoC的显着减慢我的工作效率,并在
  某些情况下,进一步复杂化的code和建筑


标记西曼写了关于这个问题的真棒博客,并回答了这个问题:
我对那种问题的第一反应是:你说的松耦合code是很难理解的。比更难?

松耦合和大图片

编辑:最后,我想指出,并非每一个物体和依赖性需求,还是应该依赖注入,首先考虑,如果你使用的是什么实际上被认为是一个依赖:

什么是依赖?


  • 应用程序配置

  • 系统资源(时钟)

  • 第三方库

  • 数据库

  • WCF /网络服务

  • 外部系统(文件/邮件)

以上任何物体或合作者可以在你的控制,并导致副作用和差异行为并使其很难测试。这些都是需要考虑的一个抽象(类/接口),并使用DI时代。

什么都没有相关性,并不真正需要DI?


  • 列表

  • 的MemoryStream

  • 字符串/基元

  • 叶对象/ DTO的

对象,如在这里使用关键字需要上面可以简单地实例化。我不会建议使用DI这种简单的对象,除非有特殊原因。考虑这样一个问题,如果对象是你的完全控制之下,不会造成任何行为上的额外的对象图表或副作用(至少要更改/控制或测试的行为的任何东西)。在这种情况下只是新起来。

我已经发布了大量的链接,马克·西曼的职位,但我真的建议你读他的书和博客文章。

Abstract

For the past few months I have been programming a light weight, C# based game engine with API abstraction and entity/component/scripting system. The whole idea of it is to ease the game development process in XNA, SlimDX and such, by providing architecture similar to that of the Unity engine.

Design challenges

As most game developers know, there are a lot of different services you need to access throughout your code. Many developers resort to using global static instances of e.g. a Render manager(or a composer), a Scene, Graphicsdevice(DX), Logger, Input state, Viewport, Window and so on. There are some alternative approaches to the global static instances/ singletons. One is to give each class an instance of the classes it needs access to, either through a constructor or constructor/property dependency injection(DI), another is to use a global service locator, like StructureMap's ObjectFactory where the service locator is usually configured as an IoC container.

Dependency Injection

I chose to go the DI way for many reasons. The most obvious one being testability, by programming against interfaces and have all the dependencies of every class provided to them through a constructor, those classes are easily tested since the test container can instantiate the required services, or the mocks of them, and feed into every class to be tested. Another reason for doing DI/IoC was, believe it or not, to increase the readability of the code. No more huge initialization process of instantiating all the different services and manually instantiating classes with references to the required services. Configuring the Kernel(NInject)/Registry(StructureMap) conveniently gives a single point of configuration for the engine/game, where service implementations are picked and configured.

My problems

  • I often feel like I am creating interfaces for interfaces sake
  • My productivity has gone down dramatically since all I do is worry about how to do things the DI-way, instead of the quick and simple global static way.
  • In some cases, e.g. when instantiating new Entities on runtime, one needs access to the IoC container / kernel to create the instance. This creates a dependency on the IoC container itself (ObjectFactory in SM, an instance of the kernel in Ninject), which really goes against the reason for using one in the first place. How can this be resolved? Abstract factories come to mind, but that just further complicates the code.
  • Depending on service requirements, some classes' constructors can get very large, which will make the class completely useless in other contexts where and if an IoC is not used.

Basically doing DI/IoC dramatically slows down my productivity and in some cases further complicates the code and architecture. Therefore I am uncertain of whether it is a path I should follow, or just give up and do things the old fashioned way. I am not looking for a single answer saying what I should or shouldn't do but a discussion on if using DI is worth it in the long run as opposed to using the global static/singleton way, possible pros and cons I have overlooked and possible solutions to my problems listed above, when dealing with DI.

解决方案

Should you go back to the old-fashioned way? My answer in short is no. DI has numerous benefits for all the reasons you mentioned.

I often feel like I am creating interfaces for interfaces sake

If you are doing this you might be violating the Reused Abstractions Principle (RAP)

Depending on service requirements, some classes' constructors can get very large, which will make the class completely useless in other contexts where and if an IoC is not used.

If your classes constructors are too large and complex, this is the best way to show you that you are violating a very important other principle: Single Reponsibility Principle. In this case it is time to extract and refactor your code into different classes, the number of dependencies suggested is around 4.

In order to do DI you don't have to have an interface, DI is just the way you get your dependencies into your object. Creating interfaces might be a needed way to be able to substitute a dependency for testing purposes. Unless the object of the dependency is:

  1. Easy to isolate
  2. Doesn't talk to external subsystems (file system etc)

You can create your dependency as an Abstract class, or any class where the methods you'd like to substitute are virtual. However interfaces do create the best de-coupled way of an dependency.

In some cases, e.g. when instantiating new Entities on runtime, one needs access to the IoC container / kernel to create the instance. This creates a dependency on the IoC container itself (ObjectFactory in SM, an instance of the kernel in Ninject), which really goes against the reason for using one in the first place. How can this be resolved? Abstract factories come to mind, but that just further complicates the code.

As far as a dependency to the IOC container, you should never have a dependency to it in your client classes. And they don't have to.

In order to first use dependency injection properly is to understand the concept of the Composition Root. This is the only place where your container should be referenced. At this point your entire object graph is constructed. Once you understand this you will realize you never need the container in your clients. As each client just gets its dependency injected.

There are also MANY other creational patterns you can follow to make construction easier: Say you want to construct an object with many dependencies like this:

new SomeBusinessObject(
    new SomethingChangedNotificationService(new EmailErrorHandler()),
    new EmailErrorHandler(),
    new MyDao(new EmailErrorHandler()));

You can create a concrete factory that knows how to construct this:

 public static class SomeBusinessObjectFactory
 {
        public static SomeBusinessObject Create()
        {
            return new SomeBusinessObject(
                    new SomethingChangedNotificationService(new EmailErrorHandler()),
                    new EmailErrorHandler(),
                    new MyDao(new EmailErrorHandler()));
        }
 }

And then use it like this:

 SomeBusinessObject bo = SomeBusinessObjectFactory.Create();

You can also use poor mans di and create a constructor that takes no arguments at all:

public SomeBusinessObject()
     {
         var errorHandler = new EmailErrorHandler();
         var dao = new MyDao(errorHandler);
         var notificationService = new SomethingChangedNotificationService(errorHandler);
         Initialize(notificationService, errorHandler, dao);
     }
     protected void Initialize(
        INotificationService notifcationService,
        IErrorHandler errorHandler,
        MyDao dao)
     {
        this._NotificationService = notifcationService;
        this._ErrorHandler = errorHandler;
        this._Dao = dao;
     }

Then it just seems like it used to work:

SomeBusinessObject bo = new SomeBusinessObject();

Using Poor Man's DI is considered bad when your default implementations are in external third party libraries, but less bad when you have a good default implementation.

Then obviously there are all the DI containers, Object builders and other patterns.

So all you need is to think of a good creational pattern for your object. Your object itself should not care how to create the dependencies, in fact it makes them MORE complicated and causes them to mix 2 kinds of logic. So I don't beleive using DI should have loss of productivity.

There are some special cases where your object cannot just get a single instance injected to it. Where the lifetime is generally shorter and on-the-fly instances are required. In this case you should inject the Factory into the object as a dependency:

public interface IDataAccessFactory
{
    TDao Create<TDao>();
}

As you can notice this version is generic because it can make use of an IoC container to create various types (Take note though the IoC container is still not visible to my client).

public class ConcreteDataAccessFactory : IDataAccessFactory
{
     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>())
     }
   }

Notice I used activator even though I had an Ioc container, this is important to note that the factory needs to construct a new instance of object and not just assume the container will provide a new instance as the object may be registered with different lifetimes (Singleton, ThreadLocal, etc). However depending on which container you are using some can generate these factories for you. However if you are certain the object is registered with Transient lifetime, you can simply resolve it.

EDIT: Adding class with Abstract Factory dependency:

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

Basically doing DI/IoC dramatically slows down my productivity and in some cases further complicates the code and architecture

Mark Seeman wrote an awesome blog on the subject, and answered the question: My first reaction to that sort of question is: you say loosely coupled code is harder to understand. Harder than what?

Loose Coupling and the Big Picture

EDIT: Finally I'd like to point out that not every object and dependency needs or should be dependency injected, first consider if what you are using is actually considered a dependency:

What are dependencies?

  • Application Configuration
  • System Resources (Clock)
  • Third Party Libraries
  • Database
  • WCF/Network Services
  • External Systems (File/Email)

Any of the above objects or collaborators can be out of your control and cause side effects and difference in behavior and make it hard to test. These are the times to consider an Abstraction (Class/Interface) and use DI.

What are not dependencies, doesn't really need DI?

  • List
  • MemoryStream
  • Strings/Primitives
  • Leaf Objects/Dto's

Objects such as the above can simply be instantiated where needed using the new keyword. I would not suggest using DI for such simple objects unless there are specific reasons. Consider the question if the object is under your full control and doesn't cause any additional object graphs or side effects in behavior (at least anything that you want to change/control the behavior of or test). In this case simply new them up.

I have posted a lot of links to Mark Seeman's posts, but I really recommend you read his book and blog posts.

这篇关于依赖注入和发展生产力的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆