Blog#97: 🔑Unlocking the Magic of Dependency Injection: A Beginner's Guide💉

image.png

Hi, I'm Tuan, a Full-stack Web Developer from Tokyo 😊. Follow my blog to not miss out on useful and interesting articles in the future.

Are you ready to take your JavaScript skills to the next level? Want to learn how to make your code more organized, easier to test, and more modular? Well, you're in luck because today we're going to dive into the world of dependency injection!

Dependency injection is a powerful design pattern that allows you to manage the relationships between different objects in your code. It helps to make your code more flexible and easier to understand, and is a must-know technique for any serious JavaScript developer.

In this article, we're going to break down the basics of dependency injection in plain English and provide examples of how it can be used to make your code more powerful. Plus, we'll include code samples for each example to help you see how it works in practice.

So what are you waiting for? Let's get started and learn how to unleash the power of dependency injection!

What is Dependency Injection?

Dependency injection is a way to provide an object with the things it needs to do its job. These things are called dependencies. Let's look at an example.

class Dog {
  constructor() {
    this.barkSound = 'woof';
  }
  
  bark() {
    console.log(this.barkSound);
  }
}

const myDog = new Dog();
myDog.bark(); // Output: 'woof'

In this example, we have a Dog class with a bark method that makes a sound. The sound is stored in a property called barkSound. Notice that the barkSound property is created inside the constructor method. It's hardcoded and if we want to change it, we need to go into the class and change it.

This is not a problem for small projects but as the project grows and we have many classes like this one, it's going to be harder to maintain. Also, if we want to test the bark method, we would need to create a new instance of the class every time and that can become a burden.

Dependency Injection

Let's see how dependency injection can help us with this problem.

class Dog {
  constructor(barkSound) {
    this.barkSound = barkSound;
  }
  
  bark() {
    console.log(this.barkSound);
  }
}

const myDog = new Dog('woof');
myDog.bark(); // Output: 'woof'

Now, instead of creating the barkSound property inside the class, we are passing it as an argument to the constructor. This allows us to change the sound that the dog makes without changing the class. It also allows us to test the bark method without creating a new instance of the class every time.

Examples

Dependency injection is a powerful pattern that can be used in many different ways. Here are five examples of how you might use it in the real world:

1. Testing:

As mentioned before, dependency injection makes it easier to write automated tests for your code. You can create a test version of a dependency and pass it to the object that you want to test. This way, you can test the object without having to test the dependency as well.

class Dog {
  constructor(barkSound) {
    this.barkSound = barkSound;
  }

  bark() {
    console.log(this.barkSound);
  }
}

class SilentDog {
  constructor() {
    this.barkSound = "";
  }

  bark() {
    console.log(this.barkSound);
  }
}

const testDog = new Dog(new SilentDog());
testDog.bark(); // Output: ''

2. Configurations:

You can use dependency injection to separate your application's configurable settings from the rest of the code. This way, you can change the behavior of your application without having to change the code itself.

class Dog {
  constructor(config) {
    this.barkSound = config.barkSound;
    this.color = config.color;
  }

  bark() {
    console.log(this.barkSound);
  }
}

const config = {
  barkSound: "woof",
  color: "brown",
};

const myDog = new Dog(config);
myDog.bark(); // Output: 'woof'
console.log(myDog.color); // Output: 'brown'

3. Plugins:

You can use dependency injection to allow users to add new functionality to your application without having to change the code. For example, you can create a plugin system that allows users to write their own plugins and add them to your application.

class MyApp {
  constructor(plugins) {
    this.plugins = plugins;
  }

  run() {
    this.plugins.forEach((plugin) => plugin.run());
  }
}

class MyPlugin {
  run() {
    console.log("This is my plugin");
  }
}

const app = new MyApp([new MyPlugin()]);
app.run(); // Output: 'This is my plugin'

4. Data Access:

You can use dependency injection to separate your application's data access code from the rest of the code. This allows you to change the way that your application stores and retrieves data without having to change the rest of the code.

class Dog {
constructor(dataAccess) {
this.dataAccess = dataAccess;
}

save() {
this.dataAccess.save(this);
}

bark() {
console.log(this.barkSound);
}
}

class LocalStorageDataAccess {
save(dog) {
localStorage.setItem('dog', JSON.stringify(dog));
}
}

const myDog = new Dog(new LocalStorageDataAccess());
myDog.barkSound = 'woof';
myDog.save();

5. Architecture:

You can use dependency injection to create a layered architecture for your application. This can help to make your code more modular and easier to understand.

class Dog {
  constructor(barkSound, color) {
    this.barkSound = barkSound;
    this.color = color;
  }
  bark() {
    console.log(`My dog barks with ${this.barkSound} and the color is ${this.color}`);
  }
}
class DogFactory {
  createDog(type) {
    switch (type) {
      case "GoldenRetriever":
        return new Dog("woof", "golden");
      case "Bulldog":
        return new Dog("bark", "white");
    }
  }
}
const factory = new DogFactory();
const goldenRetriever = factory.createDog("GoldenRetriever");
goldenRetriever.bark(); 
// Output: 'My dog barks with woof and the color is golden'

const bulldog = factory.createDog("Bulldog");
bulldog.bark(); 
// Output: 'My dog barks with bark and the color is white'

In this example, we have a DogFactory class that creates different types of dogs using the dependency injection pattern. We pass the properties barkSound and color as dependencies to the Dog class. This way, we can create different types of dogs without having to change the Dog class. And also it separates the concern of creating the dog from the Dog class.

Conclusion

Dependency injection is a powerful pattern that can help you to make your JavaScript code more modular, more testable and more flexible. It allows you to separate the concerns of your code, making it easier to understand and maintain. With dependency injection, you can create more testable and modular code, and manage the relationships between different objects in your code. It may seem a little bit complicated at first glance but once you understand the basics of it, it will make your coding journey much easier. Keep in mind that the examples provided here are simple and you can take this to another level in your project, when working with big and complex codebase, the dependency injection will prove to be extremely helpful.

As always, I hope you enjoyed this article and learned something new. Thank you and see you in the next articles!

If you liked this article, please give me a like and subscribe to support me. Thank you. 😊

NGUYỄN ANH TUẤN

Xin chào, mình là Tuấn, một kỹ sư phần mềm đang làm việc tại Tokyo. Đây là blog cá nhân nơi mình chia sẻ kiến thức và kinh nghiệm trong quá trình phát triển bản thân. Hy vọng blog sẽ là nguồn cảm hứng và động lực cho các bạn. Hãy cùng mình học hỏi và trưởng thành mỗi ngày nhé!

Đăng nhận xét

Mới hơn Cũ hơn