Dependency Injection and development productivity
Asked Answered
W

1

11

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.

Worriment answered 25/4, 2012 at 10:7 Comment(0)
D
20

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

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<T>
  • 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.

Dilation answered 25/4, 2012 at 11:33 Comment(6)
Excellent reply, right on the topic, thank you! I read both articles you linked, which both are a great source of information and actually answered a lot other questions I had in mind. The RAP violation and SRP stand out as good things to keep in mind in my case. One last thing stands out from your reply, you mention using abstract factories to hide an IoC container from your client, is it then okay for an abstract factory to depend on an IoC container?Worriment
Thanks, I am glad this helped you, and I would say yes this is very acceptable for the abstract factory to know about the container, as the factory operates in the same layer as the composition root in being the 'glue' and it doesnt contain business logic, it merely governs the concrete instances. So in a test environment you might not even use an Ioc container and you would need a different factory anyway because the concrete type would be differentDilation
Hi, I just added another edit at the end for you about understanding what is a dependency, and not to worry about any dependency management for those objects that are simple and do not require it.Dilation
+1 I'd also add that it's easy to fool yourself that the complexity of Scopes in a given ID container and all that nonsense which can feel its dragging you down into a mire is the container's fault. Its not - it's intrinsic to [Your Design of] your app. Better have it out in a Composition Root where concepts have names and can be rejigged as needed than writing reams of factory code around the place. And don't waste your time learning DI without a) reading @Mark Seemann's top rated posts and b) buying manning.com/seemannCantone
Thanks for that Andre, much appreciated. And thanks for those pointers @Ruben BartelinkWorriment
+1 This answer was the most useful thing I've ever read on dependency injection, thanksDrug

© 2022 - 2024 — McMap. All rights reserved.