قالب وردپرس درنا توس
Home / Tips and Tricks / What is Dependency Injection in Programming? – CloudSavvy IT

What is Dependency Injection in Programming? – CloudSavvy IT



Photo of code on a computer monitor

Dependency injection is a software engineering technique in which objects are passed instances of the other objects on which they depend. Rather than reaching out to dependencies themselves, objects should immediately receive everything they need to function.

A simple example

The term “dependency injection” and its definition may seem complicated. In practice it is a simple concept. Let̵

7;s take a look at two approaches to create a basic object. We’re using PHP here, but the concepts apply to all object-oriented codebases.

Without dependency injection

class UserCreator {
 
    protected Mailer $Mailer;
 
    public function __construct() {
        $this -> Mailer = new Mailer();
    }
 
    public function create(string username, string $email) : void {
        $this -> Mailer -> send($email, "Your account", "Welcome.");
    }
 
}

With dependency injection

class UserCreator {
 
    protected Mailer $Mailer;
 
    public function __construct(Mailer $mailer) {
        $this -> Mailer = $Mailer;
    }
 
    public function create(string $username, string $email) : void {
        $this -> Mailer -> send($email, "Your account", "Welcome.");
    }
 
}

The difference is subtle but significant. Both classes have one dependence on a Mailer example. The first class constructs its own Mailer, while the second works with one Mailer provided by the outside world. The Mailer dependence has been injected in the class.

Benefits of Dependency Injection

The main benefit of Dependency Injection is the decoupling of classes and their dependencies it provides. Dependency injection is one form of reversal of control – instead of classes controlling their own dependencies, they work with instances provided by their external environment.

More specifically, injecting your dependencies makes it easier to change those dependencies in the future. In a real codebase, Mailer in our example above would probably be an interface with implementations like SendmailMailer SendGridMailer and FakeMailer

Taking the first example, where classes are directly one of the Mailer cases creates extra work if you have to replace it Mailer implementation. With dependency injection you can do it Mailer interface to accept any compatible implementation. The implementation to be used is determined by the outside world.

Typically, you use dependency injection in conjunction with a dependence injection containerContainers are usually integrated with the application framework you are using. They automatically resolve and inject class dependencies.

Asking a container for a UserCreator would be one first Mailer example. This would be passed on to the UserCreator via its constructor parameter. You “wire” the container to define the implementation to use when typing an interface. Under this model, the active changes Mailer across the codebase only requires rewiring the container. This can be one line of code in the configuration of the container.

Consequences for testing

Dependency injection simplifies mocking your dependencies during testing. Since dependencies are purchased externally to the class, you can specify a bogus implementation in your unit tests:

class FakeMailer implements Mailer {
    public function send(string $to, string $subject, string $body) : void {
        return;
    }
}

We don’t need to send the welcome email when testing our UserCreatorNevertheless, in the first example it would be inevitable, true UserCreator always builds his own life MailerWe can offer a special with dependency injection Mailer which conforms to the interface contract but eliminates the side effects.

Loose coupling

Dependency Injection is not about eliminating dependencies completely – they will always be there, but they should be loosely linked. Think of a dependency as something that has been fixed for a period of time, not a fixture that has been permanently glued.

Tight coupling as seen in the first example makes for inflexible code that is difficult to move. Dependencies become opaque to outside observers, such as test runners. Type hinting interfaces passed to your class keep your dependencies as loose as possible.

One principle of responsibility

A final benefit of Dependency Injection is the ability to help you adhere to the Single Responsibility principle. This states that each class should be responsible for a single self-contained unit of functionality in your codebase.

Without injection, classes not only provide functionality, but also build their own dependencies. This requires each class to have detailed knowledge of the requirements of other subsystems. In injection, the classroom’s knowledge of the outside world is limited to the contracts provided by the interfaces on which it depends.

Conclusion

Dependency injection makes it easier to maintain object-oriented code bases. Classes that construct their own dependencies are usually a code scent. Ultimately, they are closely linked to their environment and are difficult to test.

Removing dependencies from the classes that use them reverses control and creates a stronger separation of concerns. You can think of this as akin to removing hard-coded constants: you would never write a database schema name in your source code because it should be in a configuration file.

The implementation of Mailer is also a configuration detail. UserCreator does not have to worry about setting up a mail system. Chances are you will want to reuse the same system elsewhere in your codebase. Configuration changes more often than code; you probably want to change the way email is sent long before you change the business requirement that an email be sent when a user signs in.

In summary, Dependency Injection encourages your components to ask about the functionality they need, rather than looking at it step-by-step on a case-by-case basis. Application-level instance wiring through a container exposes dependencies for what they are: configuration details that can change. Code your classes to accept abstractions from the environment, rather than concrete implementations constructed internally.


Source link