What are the differences between Dependency Injection and Control Inversion?

Asked

Viewed 8,108 times

89

Sometimes it feels like we’re talking about the same thing (of course, it’s not) when these concepts are being used. What’s the real difference between them? When using one or the other?

2 answers

80


Inversion of Control (or Ioc, Inversion of Control) is a broader term to refer to a behavior (explained below). Several ways to implement inversion of control.

Dependency injection is no longer something so broad, it is actually considered up to a design standard.

Dependency injection is one of the ways of doing Control Inversion. So "When using a or another" doesn’t make much sense.

But the confusion is very common, it is not uncommon to find the terms being used interchangeably.

I believe explaining one and then the other demystify this mess.

Inversion of Control (Ioc)

Inversion of control is a behavior present when we use some frameworks. Basically the idea is to separate *what* from *when*.

Here are two examples (abstract code), the usual form and the one with Ioc.

Usual form

perguntarNome()
lerEntrada()
perguntarIdade()
lerEntrada()
SE verificarSeDadosEstaoValidos()
ENTAO cadastrar()
SENAO exibirMensagemDeErro()

With Ioc

quandoPerguntarNome(lerEntrada)
quandoPerguntarIdade(lerEntrada)
paraVerificarValidade(verificarSeDadosEstaoValidos)
paraCadastrar(cadastrar)
quandoHouverErro(exibirMensagemDeErro)

In the second example we describe what will be done when it is done; we separate the implementations from the flow. This way it is easier to reuse or change only some part of the code (without having to modify/understand *the whole*).

Notice that there is a gap to fit several programming concepts, such as object orientation principles, event-oriented design, among other things.

I will stop here; this is in its essence what we call Inversion of Control.

Dependency injection

Dependency injection is one of the ways to perform Control Inversion.

The technique consists in passing the dependency (the service) to the dependent (the customer). This is called injection. The important thing is to understand that injected the customer service, rather than the customer himself seek out and build the service you will use.

I think nothing better than a practical example to understand this concept. Below is a (simplified) real example that I already implemented in one of the systems I worked on (C code#):

public interface ILogger
{
    void Logar(string mensagem);
}

public class LogEmArquivo : ILogger
{
    void Logar(string mensagem)
    {
        // Loga a mensagem em um arquivo de log
    }
}

public class LogPorEmail : ILogger
{
    void Logar(string mensagem)
    {
        // Envia a mensagem por email aos responsáveis
    }
}

public class Principal
{
    private ILogger _logger;

    public Principal(ILogger logger)
    {
        _logger = logger;
    }

    // Chamado pelos métodos internos da classe Principal
    // quando há necessidade de logar alguma informação
    private void LogarInformacao(string informacao)
    {
        _logger.Logar(informacao);
    }
}

The class Principal needs a ILogger to work, but it does not "see" or build the implementation it will use, it is up to who will build the class Principal. That way the implementation of the log action is undocked with whatever the main class will do.

As I said, this is a real example that I used. In my case, the application had in the configuration file a directive indicating which environment was being executed (Development, Tests, Approval, Production...). When starting, the application checked which environment and, according to this configuration, uses a logger different for each environment. Basically in Developing and Testing we used only a log file, while in Homologation and Production we used email sending (where the problems were more serious).

At runtime, programmatically, the application changed its logger implementation to be used. This is one of the advantages of having the code uncoupled.


Reading

One of the precursors to discuss the subject is the author Martin Fowler. For those who want to delve into the subject should read the articles on the subject in his blog:

42

Ioc

Ioc may not even be as created in common projects, but it is widely used in most applications. Ioc is also the Hollywood principle, where the producers tell the actors "don’t call us, we’ll call you". This means that a component/fraework take control of the application and when he needs your intervention he calls what you will have to provide. The most common case is a Event loop present in virtually all game mechanisms and window managers in the market. Some Erps also work like this, they take care of the whole process and call the parties that "users" can customize the functioning.

Framework is important here, since Ioc is the basis of frameworks.

DI

Dependency injection is just one of the ways to apply Ioc. I’ve seen numerous formal definitions but the concept is extremely simple. You are doing DI when you remove existing dependencies in a part of the program, possibly a class. It facilitates decoupling.

The best definition of DI is allow states and behaviour to be determined by passing parameters. That is, to allow you to refer to a class not yet known during the development of the current class. You eliminate dependency on a specific class. This can be done through parameters in constructors, common methods or properties of the current class.

Advantages and disadvantages

Let’s face it this is a huge facility. It serves to make application more flexible, even to test it since the test usually needs to be done with simulated premises. It is thus possible to create systems of plugins, settings, changes in execution on-the-fly, enables clear separation of responsibilities, clearing code and facilitating development and maintenance, and enables decoupling of the classes.

But there are those who say that total decoupling can bring another problem. You leave the responsibility to the user. I am one of those people who prefer pragmatism to academicism. Of course you may have some case that this total decoupling is really important, but most of the time you just want the flexibility. So my posture is to create yes to the parameter, but also provide a standard dependency. So the consumer of this class (or function, or component) need not worry about creating and passing anything as a parameter when what you want is to do exactly what that part of the application knows is appropriate and probably desired.

The only "downside" of this is that it creates a dependency for the standard implementation. But why is this bad? I think if it was bad it would not even exist frameworks. Flexibility is advantage, total independence is cheap theory. You will provide an implementation for a string as a parameter of everything that needs a string? Everything has limits. The difference between medicine and poison is the dosage.

Of course, separating the responsibilities also means spreading the parties through application, which makes it difficult to understand and eventually maintain. Readability also has to do with organization. Having the parts spread out makes reading harder. Especially because you pass having many types only to fulfill the injection and requires much greater planning than one is doing (which some will consider advantage and others, disadvantage).

I will illustrate with what Alles has already done in his reply:

public interface ILogger {
    void Logar(string mensagem);
}

public class LogEmArquivo : ILogger {
    void Logar(string mensagem) {
        //Loga a mensagem em um arquivo de log
    }
}

public class LogPorEmail : ILogger {
    void Logar(string mensagem) {
        //Envia a mensagem por email aos responsáveis
    }
}

public class Principal {
    private ILogger _logger;
    public Principal(ILogger logger) {
        _logger = logger;
    }
    public Principal() {
        _logger = new LogPorArquivo();
    }
    //Chamando métodos privados da classe Principal quando precisa logar alguma informação
    private void LogarInformacao(string informacao) {
        _logger.Logar(informacao);
    }
}

public static Aplicacao {
    public static void Main() {
        var logArq = new Principal(); //exemplo de uso
        var logMail = new Principal(new LogPorEmail()); //exemplo injetando a dependência
    }
}

I put in the Github for future reference.

The use of these features should serve the application and not external interests such as the need for testing, for example. When testing, use a design standard suitable for testing. Tests can be obtained with good tools, not with the way you create code. Organizing the code in a way just because it facilitates the test is something wrong, IMHO. Doing it because it facilitates maintenance and by chance facilitates the test is ok. If the test tool you use requires a specific encoding, change the tool.

Remembering that we must always program to the interface and not to the implementation.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.