+7

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

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 and provide examples of how it can be used to make your code more powerful.

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 some examples of how you might use it:

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(dog) {
    this.dog = dog;
  }

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

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

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

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

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.

const db = {};

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

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

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

class LocalStorageDataAccess {
  save(dog) {
    db[JSON.stringify(dog)] = JSON.stringify(dog);
  }
}

const myDog = new Dog(new LocalStorageDataAccess());
myDog.barkSound = "woof";
myDog.save();
console.log(db);
// {
//  '{"dataAccess":{},"barkSound":"woof"}': '{"dataAccess":{},"barkSound":"woof"}'
// }

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.

Mình hy vọng bạn thích bài viết này và học thêm được điều gì đó mới.

Donate mình một ly cafe hoặc 1 cây bút bi để mình có thêm động lực cho ra nhiều bài viết hay và chất lượng hơn trong tương lai nhé. À mà nếu bạn có bất kỳ câu hỏi nào thì đừng ngại comment hoặc liên hệ mình qua: Zalo - 0374226770 hoặc Facebook. Mình xin cảm ơn.

Momo: NGUYỄN ANH TUẤN - 0374226770

TPBank: NGUYỄN ANH TUẤN - 0374226770 (hoặc 01681423001)

image.png


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí