2

Say we have a class structure like the following:

// implementation.ts
export class A implements IA {
    constructor(private b: IB) {}
}
export class B implements IB {
    constructor(private c: IC) {}
}
export class C implements IC {
}
// ... interface declarations

Using an IoC container, it is possible to inject dependencies using annotations like @Inject or @AutoWired, however I believe there are two caveats:

  • Using annotations adds further concerns to my classes. A, B and C should only be responsible for the implementation and not for what should be implemented in which manner.
  • We risk to encounter runtime errors because the IoC container does not support compile-time checks whether each dependency is met.

So I thought a bit and came up with an additional layer responsible for dependency injection:

// dependency-injection.ts
let impl = require("./implementation");
export class A extends impl.A {
    constructor() {
        super(new B());
}
export class B extends impl.B {
    constructor() {
        super(new B());
}
export class C extends impl.C {
}

To instatiate A with the injected dependencies in my application on startup, I would simply call new diModule.A(). This solution has many advantages:

  • does not require any framework
  • supports compile-time checks whether a dependency is met
  • separates the concerns of implementation and wiring
  • is very flexible, i.e. does not constrain the way of wiring

...while keeping the advantages of an IoC container which come to my mind:

  • allows sophisticated dependency injection
  • avoids constructor nesting: new A(new B(new C()))
  • when updating a dependency, I have to change one and only one class
  • it is quite easy to apply different implementations for different purposes, for example by having two classes LoggerForWarnings and LoggerForErrors.

Which of these two aspect-oriented approaches would you prefer and why? Is there a name for my approach? Are there further disadvantages of the DI layer or further advantages of an IoC container?


Having decided which answer to accept, I'll try to summarize some aspects of the two answers. I discussed a lot with Adrian Iftode and although I'm still critical about IoC containers without compile-time dependency checks, his arguments were worth a +1 - but unfortunately, I'm missing the reputation.

  • Christian Willman noted that for projects which don't exceed a certain size and complexity, my approach is often realized without all the class foo, simply wiring all the classes by constructor injection at composition root directly.
  • Adrian Iftode remarked that IoC containers can do much more than the classes A, B and C can - for example, they can manage the lifetime of objects or provide singletons. This functionality could be added to my example by using factories instead of constructors in the DI layer.
  • What I'm still wondering about is if the approach is also useful for larger projects. In my opinion, directly doing constructor injection in main() will be quite difficult to maintain while creating several factories in the DI layer could improve maintainability whilst keeping the advantages of other IoC containers.

Thank you two very much!

0

2 Answers 2

2

You can actually get away with a much simpler implementation. Just manually wire everything together in the so-called composition root, which is just the application's entrypoint.

Of course you need to be mindful of the dependency resolution order and first instantiate the classes which themselves have no dependencies (that is, the sinks in the object dependency graph).

In your example:

function main() {
  const c: C = new C();
  const b: B = new B(c);
  const a: A = new A(b);
}

The benefits are twofold. You don't clutter your classes with default constructors, and the (brutally) straightforward instantiation process makes the code flow obvious to maintainers.

4
  • I really like that hint, it really seems to me to be a more pragmatic approach and I think it's a good candidate to be accepted - but when do you favor the composition root approach over an IoC container and why?
    – bloxx
    Aug 11, 2016 at 11:08
  • 1
    @bloxx a good IoC container constructs the graph at the composition root as well. There's just more magic to it.
    – RubberDuck
    Aug 11, 2016 at 19:14
  • I manually wire the object graph in small, simple programs. I rarely work with software large enough to really warrant the power of something like Guice or Spring. In my mind, simpler is better and easier to understand for the next person.
    – user169469
    Aug 11, 2016 at 23:46
  • 1
    Accepted for answering the question "Is there a name for my approach?" and the simplified implementation.
    – bloxx
    Aug 12, 2016 at 22:46
0

You are creating the so-called default constructors. When people start using DI they provide two constructors: one to inject the dependencies and a default one where they create the dependencies. When you are newing a dependency you do a tight coupling with that dependency so this is not dependency inversion anymore.

7
  • So basically you're saying you don't like the second example because it contains the new keyword? Aug 10, 2016 at 22:33
  • I think you misunderstood something. Yes, I do a tight coupling. But as far as I understand it is dependency inversion: The implementation exists in a different layer that the dependency-injected classes. Calling new diModule.A() corresponds to calling ioc.resolve(A).
    – bloxx
    Aug 10, 2016 at 22:37
  • Not exactly. If you create the instances graph in your application start, then this is ok, this is DI without an IoC container. If you create dependencies (use new to create a dependency) in your base code, then is not DI anymore. That doesn't mean new is prohibited now. See this discussion for a detailed explanation programmers.stackexchange.com/questions/102023/… Aug 10, 2016 at 22:40
  • Yes, I think I have to clarify that. Of course, if I refer to my DI module from the implementation to be dependency-injected, that's counterproductive. You wouldn't do that with an IoC container, too. It's only for startup.
    – bloxx
    Aug 10, 2016 at 22:42
  • Think at constructors with 4, 5 arguments and each might have a few. Then is not only newing things, an IoC might manage and the number of instances of a service (a singleton), the lifetime of dependencies (do I reuse a dependency or should create a new one everytime?). Also when I change an implementation I have to change then every class that is using it. Aug 10, 2016 at 22:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.