Depends Injection (DI)

Mushegh Zakaryan
6 min readSep 20, 2021

Hi there, after few months I have decided to write another informational blog about one of the important things in software engineering and it is Dependency Injection (DI).

Let’s start with question:

What is Dependency Injection?

Dependency injection is one form of the broader technique of inversion of control. Dependency injection is a technique in which an object receives other objects that it depends on, called dependencies.

A client who wants to call some services should not have to know how to construct those services. Instead, the client delegates to external code (the injector). The client is not aware of the injector. The injector passes the services, which might exist or be constructed by the injector itself, to the client. The client then uses the services.

This means the client does not need to know about the injector, how to construct the services, or even which services it is using. The client only needs to know the interfaces of the services, because these define how the client may use the services. This separates the responsibility of ‘use’ from the responsibility of ‘construction’.

We can live without DI; many projects do this. But when using DI, you can break the blockers and make your code decoupled and much flexible.

Specifically, in the paradigm of Robert C Martin’s SOLID principles of Object Oriented Design, DI is one of the possible implementations of the Dependency Inversion Principle (DIP). The DIP is the D of the SOLID mantra — other DIP implementations include the Service Locator, and Plugin patterns.

Imagine the following code:

class Foo {
}

class Bar {
foo: Foo;

constructor() {
this.foo = new Foo();
}
}

class Foobar {
foo: Foo;
bar: Bar;

constructor() {
this.foo = new Foo();
this.bar = new Bar();
}
}

This is bad for multiple reasons like having direct and non-exchangeble dependencies between classes, testing would be really hard, following your code becomes really hard, re-usability of components becomes harder, etc.. Dependency Injection on the other hand injects dependencies into your constructor, making all these bad things obsolete:

class Foo {
}

class Bar {
constructor(foo: Foo) {
}
}

class Foobar {
constructor(foo: Foo, bar: Bar) {
}
}

The best analogy I can think of is the surgeon and his assistant(s) in an operation theater, where the surgeon is the main person and his assistant who provides the various surgical components when he needs it so that the surgeon can concentrate on the one thing he does best (surgery). Without the assistant the surgeon has to get the components himself every time he needs one.

DI for short, is a technique to remove a common additional responsibility (burden) on components to fetch the dependent components, by providing them to it.

DI brings you closer to the Single Responsibility (SR) principle, like the surgeon who can concentrate on surgery.

When to use DI : I would recommend using DI in almost all production projects ( small/big), particularly in ever changing business environments :)

Why : Because you want your code to be easily testable, mockable etc so that you can quickly test your changes and push it to the market. Besides why would you not when you there are lots of awesome free tools/frameworks to support you in your journey to a codebase where you have more control.

Understanding with an example PHP#

This is a real life example comparing a classic implementation (using new or singletons) VS using dependency injection.

Without dependency injection#

Say you have:

class GoogleMaps
{
public function getCoordinatesFromAddress($address) {
// calls Google Maps webservice
}
}
class OpenStreetMap
{
public function getCoordinatesFromAddress($address) {
// calls OpenStreetMap webservice
}
}

The classic way of doing things is:

class StoreService
{
public function getStoreCoordinates($store) {
$geolocationService = new GoogleMaps();
// or $geolocationService = GoogleMaps::getInstance() if you use singletons
return $geolocationService->getCoordinatesFromAddress($store->getAddress());
}
}

Now we want to use the OpenStreetMap instead of GoogleMaps, how do we do? We have to change the code of StoreService, and all the other classes that use GoogleMaps.

Without dependency injection, your classes are tightly coupled to their dependencies.

With dependency injection#

The StoreService now uses dependency injection:

class StoreService {
private $geolocationService;
public function __construct(GeolocationService $geolocationService) {
$this->geolocationService = $geolocationService;
}
public function getStoreCoordinates($store) {
return $this->geolocationService->getCoordinatesFromAddress($store->getAddress());
}
}

And the services are defined using an interface:

interface GeolocationService {
public function getCoordinatesFromAddress($address);
}
class GoogleMaps implements GeolocationService { ...class OpenStreetMap implements GeolocationService { ...

Now, it is for the user of the StoreService to decide which implementation to use. And it can be changed anytime, without having to rewrite the StoreService.

The StoreService is no longer tightly coupled to its dependency.

Let me ask you last question :)

What are the benefits of using Dependency Injection?

First of all from Wiki:

Advantages and disadvantages

Advantages

A basic benefit of dependency injection is decreased coupling between classes and their dependencies. By removing a client’s knowledge of how its dependencies are implemented, programs become more reusable, testable and maintainable.

This also results in increased flexibility: a client may act on anything that supports the intrinsic interface the client expects.

Many of dependency injection’s benefits are particularly relevant to unit-testing.

For example, dependency injection can be used to externalize a system’s configuration details into configuration files, allowing the system to be reconfigured without recompilation. Separate configurations can be written for different situations that require different implementations of components. This includes testing. Similarly, because dependency injection does not require any change in code behavior it can be applied to legacy code as a refactoring. The result is clients that are more independent and that are easier to unit test in isolation using stubs or mock objects that simulate other objects not under test. This ease of testing is often the first benefit noticed when using dependency injection.

More generally, dependency injection reduces boilerplate code, since all dependency creation is handled by a singular component.

Finally, dependency injection allows concurrent development. Two developers can independently develop classes that use each other, while only needing to know the interface the classes will communicate through. Plugins are often developed by third party shops that never even talk to the developers who created the product that uses the plugins.

Disadvantages

Criticisms of dependency injection argue that it:

  • Creates clients that demand configuration details, which can be onerous when obvious defaults are available.
  • Make code difficult to trace because it separates behavior from construction.
  • Is typically implemented with reflection or dynamic programming. This can hinder IDE automation.
  • Typically requires more upfront development effort.
  • Forces complexity out of classes and into the links between classes which might be harder to manage.
  • Encourage dependence on a framework.

Most important, for me, is making it easy to follow the Single Responsibility Principle.

DI/IoC makes it simple for me to manage dependencies between objects. In turn, that makes it easier for me to break coherent functionality off into it’s own contract (interface). As a result, my code has been far more modularized since I learned of DI/IoC.

Another result of this is that I can much more easily see my way through to a design that supports the Open-Closed Principle. This is one of the most confidence inspiring techniques (second only to automated testing). I doubt I could espouse the virtues of Open-Closed Principle enough.

DI/IoC is one of the few things in my programming career that has been a “game changer.” There is a huge gap in quality between code I wrote before & after learning DI/IoC. Let me emphasize that some more. HUGE improvement in code quality.

I hope this article was useful for you and you will make amazing applications.

Best Regards,
Mushegh Zakaryan

https://www.linkedin.com/in/mushegh-zakaryan/

--

--