1

Many people come from frameworks that implement Dependency Injection and IoC containers for everything (in my case Angular 2+), so, this group of people will try to use dependency injection and IoC containers in everything even outside those frameworks.

But, most of the implementation using DI is not using it to implement the Dependency inversion principle from SOLID (which is kind of impossible to implement without DI), they are just using it as a way of making the class easily testable doing something like this:

class Dependency {}

class SomeClass { constructor(private dependency: Dependency) {} }

But why not this:

class Dependency {
  static global = new Dependency();
}

class SomeClass { protected dependency = Dependency.global; }

We can easily mock/stub this dependecy only by extending it:


class TestSomeClass extends SomeClass {
  protected dependency = mock<Dependency>();
}

Is there any problem we could face by doing this instead of dependency injection?

  • 2
    What's wrong with SomeClass objectUnderTest = new SomeClass(mock<Dependency>())? – Filip Milovanović Dec 01 '23 at 15:02
  • "most of the implementation using DI is not using it to implement the Dependency inversion principle from SOLID" - sure, a lot of times this is an afterthought; for dependency inversion to work, you have to be careful how you design the abstraction in the middle. But also, testing is one use case for that dependency inversion (your class not depending on the concrete implementation of the dependency is the reason why you can substitute a mock in the first place). Another thing this allows you to do is to control dependency direction between layers. – Filip Milovanović Dec 01 '23 at 15:07
  • @FilipMilovanović The problem is that your application will need:

    a IoC container or will need to have a class/file just to inject dependency or will need to pass dependency each time it needs to consume a class.

    It's kind of add additional work that can be avoided if you are not depending on abstractions.

    – Vitor Figueredo Marques Dec 01 '23 at 15:09
  • 2
    An IoC container is optional (google "Pure DI"), though in practice you'll often use one. The problem with a protected prop is that it's not dynamically configurable. Sometimes you want that, sometimes you don't. The problem with setting it from a global is, well, that it's a global (it's basically a variant of the Service Locator antipattern). It's like having a function using a global internally. – Filip Milovanović Dec 01 '23 at 15:17
  • "It's kind of add additional work" - If the application is simple enough and you don't need to spend a lot of time maintaining it, you might not need to think that much about design or DI, etc., so the additional work is not worth it. But if you are going to maintain it, then if you don't get the design right (coupling, cohesion, etc.), there's a potential to end up with a lot more (and I mean a lot more) additional work. Not guaranteed to happen, but a risk worth assessing. – Filip Milovanović Dec 01 '23 at 15:18
  • I said additional work, but what I mean is that it is unnecessary work, if you are not depending on an abstraction and there is a better way of making a class testable, there is no need for having the work of manually injecting dependencies.

    But, injecting the same way @Doc Brown showed is really a better alternative.

    – Vitor Figueredo Marques Dec 01 '23 at 15:30
  • Your idea isn't unsound. Using an overrideable property is a way to provide dependencies. This is a limited variant of the template method pattern. But you'll run into difficulties when you have multiple classes that depend on each other. You'd have to override all of them, which gets pretty tedious. You could bundle all deps into a single class that is then passed around, which would simply things, but at that point you reinvented the DI container. – amon Dec 01 '23 at 23:22

1 Answers1

4

It will be easier (and avoids the unneccessary usage of inheritance) by using an optional constructor parameter with a default value:

class SomeClass {
   constructor(private dependency = new Dependency()) {}
}

This is known as Bastard Injection - and as you see from that former link, even Mark Seemann (author of several well-known books about DI) thinks this is ok as long as "Dependency" is not a foreign dependency. It does not cause more or less dependencies than your proposed solution, requires less code and is simpler. For testing, you can stay with

  SomeClass objectUnderTest = new SomeClass(mock<Dependency>())

just as suggest by Filip Milovanović in a comment. And no, you don't need an IoC container for this.

Disclaimer: I am not a Typescript guy, I had to lookup the docs if Typescript supports this, it seems it does. If I made a syntactical error, feel free to fix it.

Doc Brown
  • 206,877