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
andLoggerForErrors
.
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
andC
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!