it-swarm-pt.tech

Qual é o princípio da inversão de dependência e por que é importante?

Qual é o princípio da inversão de dependência e por que é importante?

163
Phillip Wells

Verifique este documento: O Princípio da Inversão de Dependência .

Basicamente diz:

  • Módulos de alto nível não devem depender de módulos de baixo nível. Ambos devem depender de abstrações.
  • As abstrações nunca devem depender de detalhes. Detalhes devem depender de abstrações.

Quanto ao motivo pelo qual é importante, em resumo: as alterações são arriscadas e, dependendo de um conceito em vez de uma implementação, você reduz a necessidade de mudança nos locais de chamada.

Efetivamente, o DIP reduz o acoplamento entre diferentes partes do código. A ideia é que, embora existam muitas maneiras de implementar, digamos, um recurso de registro, a maneira como você o usaria deve ser relativamente estável no tempo. Se você puder extrair uma interface que represente o conceito de log, essa interface deve ser muito mais estável no tempo do que sua implementação, e os sites de chamada devem ser muito menos afetados pelas alterações que você poderia fazer enquanto mantém ou estende esse mecanismo de log.

Ao também tornar a implementação dependente de uma interface, você tem a possibilidade de escolher em tempo de execução qual implementação é mais adequada para seu ambiente específico. Dependendo dos casos, isso pode ser interessante também.

102
Carl Seleborg

Quando projetamos aplicativos de software, podemos considerar as classes de baixo nível as classes que implementam operações básicas e primárias (acesso ao disco, protocolos de rede, ...) e classes de alto nível as classes que encapsulam lógica complexa (fluxos de negócios, ...).

Os últimos dependem das classes de baixo nível. Uma maneira natural de implementar tais estruturas seria escrever classes de baixo nível e uma vez que as tenhamos para escrever as classes complexas de alto nível. Como as classes de alto nível são definidas em termos de outras, isso parece ser a maneira lógica de fazê-lo. Mas este não é um design flexível. O que acontece se precisarmos substituir uma classe de baixo nível?

O Princípio de Inversão de Dependência afirma que:

  • Módulos de alto nível não devem depender de módulos de baixo nível. Ambos devem depender de abstrações.
  • Abstrações não devem depender de detalhes. Detalhes devem depender de abstrações.

Este princípio procura "inverter" a noção convencional de que módulos de alto nível em software devem depender dos módulos de nível inferior. Aqui, os módulos de alto nível possuem a abstração (por exemplo, decidindo os métodos da interface) que são implementados pelos módulos de nível inferior. Assim, tornar módulos de nível inferior dependentes de módulos de nível superior.

10
nikhil.singhal

A inversão de dependência bem aplicada oferece flexibilidade e estabilidade no nível de toda a arquitetura da sua aplicação. Isso permitirá que seu aplicativo evolua de forma mais segura e estável.

Arquitetura em camadas tradicional

Tradicionalmente, uma interface de usuário de arquitetura em camadas dependia da camada de negócios e, por sua vez, dependia da camada de acesso a dados.

http://xurxodev.com/content/images/2016/02/Traditional-Layered.png

Você precisa entender a camada, o pacote ou a biblioteca. Vamos ver como o código seria.

Nós teríamos uma biblioteca ou pacote para a camada de acesso a dados.

// DataAccessLayer.dll
public class ProductDAO {

}

E outra lógica de negócios de biblioteca ou camada de pacote que depende da camada de acesso a dados.

// BusinessLogicLayer.dll
using DataAccessLayer;
public class ProductBO { 
    private ProductDAO productDAO;
}

Arquitetura em camadas com inversão de dependência

A inversão de dependência indica o seguinte:

Módulos de alto nível não devem depender de módulos de baixo nível. Ambos devem depender de abstrações.

Abstrações não devem depender de detalhes. Detalhes devem depender de abstrações.

Quais são os módulos de alto nível e baixo nível? Pensando em módulos como bibliotecas ou pacotes, módulo de alto nível seriam aqueles que tradicionalmente possuem dependências e baixo nível de que dependem.

Em outras palavras, o nível alto do módulo seria onde a ação é invocada e o nível baixo onde a ação é executada.

Uma conclusão razoável a partir deste princípio é que não deve haver dependência entre concreções, mas deve haver uma dependência de uma abstração. Mas, de acordo com a abordagem que adotamos, podemos estar aplicando mal a dependência da dependência do investimento, mas uma abstração.

Imagine que adaptamos nosso código da seguinte maneira:

Teríamos uma biblioteca ou pacote para a camada de acesso a dados que define a abstração.

// DataAccessLayer.dll
public interface IProductDAO
public class ProductDAO : IProductDAO{

}

E outra lógica de negócios de biblioteca ou camada de pacote que depende da camada de acesso a dados.

// BusinessLogicLayer.dll
using DataAccessLayer;
public class ProductBO { 
    private IProductDAO productDAO;
}

Embora dependamos de uma abstração, a dependência entre os negócios e o acesso a dados permanece a mesma.

http://xurxodev.com/content/images/2016/02/Traditional-Layered.png

Para obter a inversão de dependência, a interface de persistência deve ser definida no módulo ou pacote onde essa lógica ou domínio de alto nível é e não no módulo de baixo nível.

Primeiro defina o que é a camada de domínio e a abstração de sua comunicação é a persistência definida.

// Domain.dll
public interface IProductRepository;

using DataAccessLayer;
public class ProductBO { 
    private IProductRepository productRepository;
}

Depois que a camada de persistência depende do domínio, conseguir inverter agora se uma dependência é definida.

// Persistence.dll
public class ProductDAO : IProductRepository{

}

http://xurxodev.com/content/images/2016/02/Dependency-Inversion-Layers.png

Aprofundando o princípio

É importante assimilar bem o conceito, aprofundando o propósito e os benefícios. Se permanecermos mecanicamente e aprendermos o repositório típico de casos, não seremos capazes de identificar onde podemos aplicar o princípio da dependência.

Mas por que invertemos uma dependência? Qual é o objetivo principal além dos exemplos específicos?

Tal comumente permite que as coisas mais estáveis, que não dependem de coisas menos estáveis, mudem com mais frequência.

É mais fácil para o tipo de persistência ser alterado, seja o banco de dados ou a tecnologia para acessar o mesmo banco de dados que a lógica do domínio ou ações projetadas para se comunicar com persistência. Por causa disso, a dependência é revertida porque é mais fácil alterar a persistência se essa alteração ocorrer. Desta forma não teremos que mudar o domínio. A camada de domínio é a mais estável de todas, e é por isso que não deve depender de nada.

Mas não existe apenas este exemplo de repositório. Existem muitos cenários onde este princípio se aplica e existem arquiteturas baseadas neste princípio.

Arquiteturas

Existem arquiteturas onde a inversão de dependência é fundamental para sua definição. Em todos os domínios é o mais importante e são abstrações que indicarão o protocolo de comunicação entre o domínio e o resto dos pacotes ou bibliotecas.

Arquitetura Limpa

Em Limpar arquitetura o domínio está localizado no centro e se você olhar na direção das setas indicando dependência, fica claro quais são as camadas mais importantes e estáveis. As camadas externas são consideradas ferramentas instáveis, portanto, evite depender delas.

Arquitetura Hexagonal

Acontece da mesma forma com a arquitetura hexagonal, onde o domínio também está localizado na parte central e os portos são abstrações de comunicação do dominó para fora. Aqui, novamente, é evidente que o domínio é a dependência mais estável e tradicional é invertida.

9
xurxodev

Para mim, o Princípio de Inversão de Dependência, conforme descrito no artigo oficial , é realmente uma tentativa equivocada de aumentar a capacidade de reutilização de módulos que são inerentemente menos reutilizáveis, bem como uma maneira de contornar um problema no C++. língua.

O problema em C++ é que os arquivos de cabeçalho normalmente contêm declarações de campos e métodos privados. Portanto, se um módulo C++ de alto nível incluir o arquivo de cabeçalho de um módulo de baixo nível, ele dependerá do implementação detalhes desse módulo. E isso, obviamente, não é uma coisa boa. Mas isso não é um problema nas linguagens mais modernas comumente usadas hoje em dia.

Os módulos de alto nível são inerentemente menos reutilizáveis ​​do que os módulos de baixo nível, porque os primeiros são normalmente mais específicos do que o segundo. Por exemplo, um componente que implementa uma tela de interface do usuário é do mais alto nível e também muito (completamente?) Específico do aplicativo. Tentar reutilizar esse componente em um aplicativo diferente é contraproducente e pode levar apenas ao excesso de engenharia.

Assim, a criação de uma abstração separada no mesmo nível de um componente A que depende de um componente B (que não depende de A) só pode ser feita se o componente A for realmente útil para reutilização em diferentes aplicações ou contextos. Se esse não for o caso, aplicar o DIP seria um projeto ruim.

9
Rogério

Basicamente diz:

A classe deve depender de abstrações (por exemplo, interface, classes abstratas), não detalhes específicos (implementações).

8
martin.ra

Uma maneira muito mais clara de declarar o Princípio da Inversão de Dependência é:

Seus módulos que encapsulam lógica de negócios complexa não devem depender diretamente de outros módulos que encapsulam a lógica de negócios. Em vez disso, eles devem depender apenas de interfaces para dados simples.

Ou seja, em vez de implementar sua classe Logic como as pessoas geralmente fazem:

class Dependency { ... }
class Logic {
    private Dependency dep;
    int doSomething() {
        // Business logic using dep here
    }
}

você deveria fazer algo como:

class Dependency { ... }
interface Data { ... }
class DataFromDependency implements Data {
    private Dependency dep;
    ...
}
class Logic {
    int doSomething(Data data) {
        // compute something with data
    }
}

Data e DataFromDependency devem estar no mesmo módulo que Logic, não com Dependency.

Por que fazer isso?

  1. Os dois módulos de lógica de negócios estão agora desacoplados. Quando Dependency muda, você não precisa alterar Logic
  2. Entender o que Logic faz é uma tarefa muito mais simples: ele opera somente no que parece ser um ADT.
  3. Logic agora pode ser mais facilmente testado. Agora você pode instanciar diretamente o Data com dados falsos e passá-lo para dentro. Não há necessidade de simulação ou de andaimes de teste complexos.
5
mattvonb

Boas respostas e bons exemplos já são dados por outros aqui.

A razão DIP é importante porque garante o princípio OO "design fracamente acoplado".

Os objetos em seu software NÃO devem entrar em uma hierarquia onde alguns objetos são os de nível superior, dependentes de objetos de baixo nível. Mudanças em objetos de baixo nível irão então se propagar para seus objetos de nível superior, o que torna o software muito frágil para mudanças.

Você quer que seus objetos 'de nível superior' sejam muito estáveis ​​e não frágeis para mudanças, portanto você precisa inverter as dependências.

5
Hace

Inversão de controle (IoC) é um padrão de design em que um objeto recebe sua dependência por uma estrutura externa, em vez de solicitar uma estrutura para sua dependência.

Exemplo de pseudocódigo usando a pesquisa tradicional:

class Service {
    Database database;
    init() {
        database = FrameworkSingleton.getService("database");
    }
}

Código semelhante usando IoC:

class Service {
    Database database;
    init(database) {
        this.database = database;
    }
}

Os benefícios do IoC são:

  • Você não tem dependência de uma estrutura central , Portanto, isso pode ser alterado se Desejado.
  • Como os objetos são criados Por injeção, de preferência usando interfaces , É fácil criar testes unitários Que substituam dependências por versões falsas .
  • Desacoplar o código.
5
Staale

O ponto da inversão de dependência é fazer um software reutilizável.

A ideia é que, em vez de dois pedaços de código confiarem uns nos outros, eles dependem de alguma interface abstrata. Então você pode reutilizar qualquer peça sem o outro.

A maneira mais comum de obter isso é através de um contêiner de inversão de controle (IoC), como o Spring em Java. Nesse modelo, as propriedades dos objetos são configuradas por meio de uma configuração XML, em vez de os objetos saírem e encontrarem sua dependência.

Imagine esse pseudocódigo ...

public class MyClass
{
  public Service myService = ServiceLocator.service;
}

MyClass depende diretamente da classe Service e da classe ServiceLocator. Ele precisa de ambos, se você quiser usá-lo em outro aplicativo. Agora imagine isso ...

public class MyClass
{
  public IService myService;
}

Agora, o MyClass conta com uma única interface, a interface IService. Nós deixamos o contêiner IoC realmente definir o valor dessa variável.

Então, agora, o MyClass pode ser facilmente reutilizado em outros projetos, sem trazer a dependência dessas outras duas classes junto com ele.

Melhor ainda, você não precisa arrastar as dependências do MyService, e as dependências dessas dependências, e o ... bem, você entendeu.

1
Marc Hughes

Inversão de contêineres de controle e o padrão de injeção de dependência por Martin Fowler também é uma boa leitura. Eu encontrei Head First Design Patterns um livro incrível para a minha primeira incursão em aprender DI e outros padrões.

1
Chris Canal

Eu acho que tenho um exemplo muito melhor (mais intuitivo).

  • Imagine um sistema (webapp) com gerenciamento de funcionários e contatos (duas telas).
  • Eles não são exatamente relacionados, assim você os quer em seu próprio módulo/pasta

Assim, você teria um ponto de entrada "principal" que teria que conhecer o módulo de gerenciamento de contatos do módulo de gerenciamento do Funcionário e, e teria que fornecer links na navegação e aceitar solicitações de API etc. Em outras palavras, O módulo principal dependeria desses dois - ele saberia sobre seus controladores, rotas e links que devem ser renderizados na navegação (compartilhada).

Exemplo de Node.js

// main.js
import express from 'express'

// two modules, each having many exports
import { api as contactsApi, navigation as cNav } from './contacts/'
import { api as employeesApi, navigation as eNav } from './employees/'

const api = express()
const navigation = {
  ...cNav,
  ...eNav
}

api.use('contacts', contactsApi)
api.use('employees', employeesApi)

// do something with navigation, possibly do some other setup

Além disso, por favor, note há casos (simples), quando isso é totalmente bem.


Assim, ao longo do tempo, chegará a um ponto em que não é tão trivial adicionar novos módulos. Você tem que lembrar de registrar a API, navegação, talvez permissões, e esta main.js fica maior e maior.

E é aí que entra a inversão de dependência. Em vez de seu módulo principal depender de todos os outros módulos, você introduzirá alguns "núcleos" e fará com que cada módulo se registre.

Então, neste caso, trata-se de ter uma noção de algum ApplicationModule, que pode se submeter a muitos serviços (rotas, navegação, permissões) e o módulo principal pode permanecer simples (basta importar o módulo e deixá-lo instalar)

Em outras palavras, trata-se de criar uma arquitetura conectável. Este é um trabalho extra e código que você terá que escrever/ler e manter, então você não deve fazê-lo de antemão, mas sim quando você tem esse tipo de cheiro.

O que é especialmente interessante é que você pode transformar qualquer coisa em um plugin, até mesmo na camada de persistência - o que pode valer a pena se você precisar dar suporte a muitas implementações de persistência, mas isso geralmente não é o caso. Veja a outra resposta para a imagem com arquitetura hexagonal, é ótimo para ilustração - existe um núcleo e todo o resto é essencialmente um plugin.

0
Kamil Tomšík

Inversão de dependência: depende de abstrações, não de concreções.

Inversão de controle: Main vs Abstraction, e como o Main é a cola dos sistemas.

DIP and IoC

Estes são alguns bons posts falando sobre isso:

https://coderstower.com/2019/03/26/dependency-inversion-why-you-shouldnt-avoid-it/

https://coderstower.com/2019/04/02/main-and-abstraction-the-decoupled-peers/

https://coderstower.com/2019/04/09/inversion-of-control-putting-all-together/

0
Daniel Andres Pelaez Lopez

Além de outras respostas ....

Deixe-me apresentar um exemplo primeiro ..

Haja um Hotel que peça um Gerador de Alimentos para seus suprimentos. O Hotel dá o nome da comida (digamos frango) ao Gerador de Alimentos e o Gerador devolve a comida pedida ao Hotel. Mas o hotel não se importa com o tipo de comida que recebe e serve. Assim, o Gerador fornece os alimentos com um rótulo de "Comida" para o Hotel.

Esta implementação está em Java

FactoryClass com um método de fábrica. O Gerador De Alimentos

public class FoodGenerator {
    Food food;
    public Food getFood(String name){
        if(name.equals("fish")){
            food =  new Fish();
        }else if(name.equals("chicken")){
            food =  new Chicken();
        }else food = null;

        return food;
    }
}


Um resumo/classe de interface

public abstract class Food {

    //None of the child class will override this method to ensure quality...
    public void quality(){
        String fresh = "This is a fresh " + getName();
        String tasty = "This is a tasty " + getName();
        System.out.println(fresh);
        System.out.println(tasty);
    }
    public abstract String getName();
}


Frango implementa o alimento (uma classe de concreto)

public class Chicken extends Food {
    /*All the food types are required to be fresh and tasty so
     * They won't be overriding the super class method "property()"*/

    public String getName(){
        return "Chicken";
    }
}


Peixe implementa o alimento (uma classe de concreto)

public class Fish extends Food {
    /*All the food types are required to be fresh and tasty so
     * They won't be overriding the super class method "property()"*/

    public String getName(){
        return "Fish";
    }
}


Finalmente

O hotel

public class Hotel {

    public static void main(String args[]){
        //Using a Factory class....
        FoodGenerator foodGenerator = new FoodGenerator();
        //A factory method to instantiate the foods...
        Food food = foodGenerator.getFood("chicken");
        food.quality();
    }
}

Como você pode ver, o Hotel não sabe se é um Objeto de Galinha ou um Objeto de Peixe. Ele só sabe que é um objeto de alimento, ou seja, o hotel depende da classe de alimentos.

Além disso, você notaria que o Fish and Chicken Class implementa o Food Class e não está diretamente relacionado ao Hotel. ou seja, frango e peixe também depende da classe de alimentos.

O que implica que o componente de alto nível (hotel) e o componente de baixo nível (peixe e frango) dependem de uma abstração (comida).

Isso é chamado de Inversão de Dependência. 

0
Revolver

O Princípio de Inversão de Dependência (DIP) diz que 

i) Módulos de alto nível não devem depender de módulos de baixo nível. Ambos devem depender de abstrações.

ii) As abstrações nunca devem depender de detalhes. Detalhes devem depender de abstrações.

Exemplo: 

    public interface ICustomer
    {
        string GetCustomerNameById(int id);
    }

    public class Customer : ICustomer
    {
        //ctor
        public Customer(){}

        public string GetCustomerNameById(int id)
        {
            return "Dummy Customer Name";
        }
    }

    public class CustomerFactory
    {
        public static ICustomer GetCustomerData()
        {
            return new Customer();
        }
    }

    public class CustomerBLL
    {
        ICustomer _customer;
        public CustomerBLL()
        {
            _customer = CustomerFactory.GetCustomerData();
        }

        public string GetCustomerNameById(int id)
        {
            return _customer.GetCustomerNameById(id);
        }
    }

    public class Program
    {
        static void Main()
        {
            CustomerBLL customerBLL = new CustomerBLL();
            int customerId = 25;
            string customerName = customerBLL.GetCustomerNameById(customerId);


            Console.WriteLine(customerName);
            Console.ReadKey();
        }
    }

Nota: A classe deve depender de abstrações como classes de interface ou abstratas, não detalhes específicos (implementação de interface).

0
Rejwanul Reja