Pergunta

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

Foi útil?

Solução

Confira 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.Os detalhes devem depender de abstrações.

Por que é importante, em resumo:mudanças são arriscadas e, ao depender de um conceito em vez de uma implementação, você reduz a necessidade de mudanças nos locais de atendimento.

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

Ao também fazer a implementação depender 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 também pode ser interessante.

Outras dicas

Os livros Agile Software Development, Principles, Patterns, and Practices e Agile Principles, Patterns, and Practices in C# são os melhores recursos para compreender totalmente os objetivos e motivações originais por trás do Princípio de Inversão de Dependência.O artigo "O Princípio da Inversão da Dependência" também é um bom recurso, mas por ser uma versão condensada de um rascunho que eventualmente chegou aos livros mencionados anteriormente, deixa de fora algumas discussões importantes sobre o conceito de um propriedade de pacotes e interfaces que são fundamentais para distinguir este princípio do conselho mais geral de "programar para uma interface, não para uma implementação" encontrado no livro Design Patterns (Gamma, et.al).

Para fornecer um resumo, o Princípio da Inversão de Dependência trata principalmente invertendo a direção convencional de dependências de componentes de "nível superior" para componentes de "nível inferior", de modo que os componentes de "nível inferior" dependam das interfaces controlado pelos componentes de "nível superior".(Observação:componente de "nível superior" aqui se refere ao componente que requer dependências/serviços externos, não necessariamente sua posição conceitual dentro de uma arquitetura em camadas.) Ao fazer isso, o acoplamento não é reduzido tanto quanto é mudou desde componentes que são teoricamente menos valiosos até componentes que são teoricamente mais valiosos.

Isto é conseguido projetando componentes cujas dependências externas são expressas em termos de uma interface para a qual uma implementação deve ser fornecida pelo consumidor do componente.Em outras palavras, as interfaces definidas expressam o que é necessário para o componente, não como você usa o componente (por exemplo,"INeedSomething", não "IDoSomething").

O que o Princípio da Inversão de Dependências não se refere é a simples prática de abstrair dependências através do uso de interfaces (por exemplo,MeuServiço → [ILogger ⇐ Logger]).Embora isso dissocie um componente dos detalhes específicos de implementação da dependência, não inverte a relação entre o consumidor e a dependência (por exemplo,[MeuServiço → IMyServiceLogger] ⇐ Registrador.

A importância do Princípio de Inversão de Dependência pode ser resumida em um objetivo singular de ser capaz de reutilizar componentes de software que dependem de dependências externas para uma parte de sua funcionalidade (registro, validação, etc.)

Dentro deste objetivo geral de reutilização, podemos delinear dois subtipos de reutilização:

  1. Usando um componente de software em vários aplicativos com implementações de subdependência (por exemplo,Você desenvolveu um contêiner DI e deseja fornecer registro em log, mas não deseja acoplá-lo a um criador de logs específico, de modo que todos que usam seu contêiner também tenham que usar a biblioteca de registro escolhida).

  2. Usando componentes de software dentro de um contexto em evolução (por exemplo,Você desenvolveu componentes de lógica de negócios que permanecem os mesmos em diversas versões de um aplicativo em que os detalhes de implementação estão evoluindo).

Com o primeiro caso de reutilização de componentes em vários aplicativos, como uma biblioteca de infraestrutura, o objetivo é fornecer uma necessidade básica de infraestrutura para seus consumidores sem acoplar seus consumidores a subdependências de sua própria biblioteca, uma vez que assumir dependências de tais dependências exige que você os consumidores também exijam as mesmas dependências.Isto pode ser problemático quando os consumidores da sua biblioteca optam por usar uma biblioteca diferente para as mesmas necessidades de infraestrutura (por exemplo,NLog vs.log4net) ou se optarem por usar uma versão posterior da biblioteca necessária que não seja compatível com versões anteriores da versão exigida pela sua biblioteca.

Com o segundo caso de reutilização de componentes de lógica de negócios (ou seja,"componentes de nível superior"), o objetivo é isolar a implementação do domínio principal do seu aplicativo das necessidades variáveis ​​dos detalhes da sua implementação (ou seja,alterar/atualizar bibliotecas de persistência, bibliotecas de mensagens, estratégias de criptografia, etc.).Idealmente, alterar os detalhes de implementação de um aplicativo não deve quebrar os componentes que encapsulam a lógica de negócios do aplicativo.

Observação:Alguns podem objetar a descrição deste segundo caso como reutilização real, raciocinando que componentes como componentes de lógica de negócios usados ​​em um único aplicativo em evolução representam apenas um único uso.A ideia aqui, entretanto, é que cada mudança nos detalhes de implementação do aplicativo produza um novo contexto e, portanto, um caso de uso diferente, embora os objetivos finais possam ser distinguidos como isolamento vs.portabilidade.

Embora seguir o Princípio da Inversão de Dependência neste segundo caso possa oferecer algum benefício, deve-se notar que seu valor aplicado a linguagens modernas como Java e C# é muito reduzido, talvez ao ponto de ser irrelevante.Conforme discutido anteriormente, o DIP envolve a separação completa dos detalhes de implementação em pacotes separados.No entanto, no caso de um aplicativo em evolução, a simples utilização de interfaces definidas em termos do domínio de negócios evitará a necessidade de modificar componentes de nível superior devido a mudanças nas necessidades de componentes detalhados de implementação, mesmo que os detalhes de implementação, em última análise, estejam dentro do mesmo pacote.Esta parte do princípio reflete aspectos que eram pertinentes à linguagem em vista quando o princípio foi codificado (ou seja,C++) que não são relevantes para linguagens mais recentes.Dito isto, a importância do Princípio de Inversão de Dependência reside principalmente no desenvolvimento de componentes/bibliotecas de software reutilizáveis.

Uma discussão mais longa deste princípio no que se refere ao uso simples de interfaces, injeção de dependência e padrão de interface separada pode ser encontrada aqui.Além disso, uma discussão sobre como o princípio se relaciona com linguagens de tipo dinâmico, como JavaScript, pode ser encontrada aqui.

Quando projetamos aplicações 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 as classes de alto nível as classes que encapsulam lógica complexa (fluxos de negócios, ...).

Os últimos contam com as 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, escrever as classes complexas de alto nível.Como as classes de alto nível são definidas em termos de outras, esta 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 da 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.
  • As abstrações não devem depender de detalhes.Os 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 deveriam depender dos módulos de nível inferior.Aqui, os módulos de alto nível possuem a abstração (por exemplo, decidir os métodos da interface) que são implementados pelos módulos de nível inferior.Tornando assim os módulos de nível inferior dependentes de módulos de nível superior.

Para mim, o Princípio da 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 forma de solucionar um problema na linguagem C++.

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 para um módulo de baixo nível, isso dependerá da situação real. 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.

Módulos de alto nível são inerentemente menos reutilizáveis ​​do que módulos de baixo nível porque os primeiros são normalmente mais específicos da aplicação/contexto do que os últimos.Por exemplo, um componente que implementa uma tela de UI é do mais alto nível e também muito (completamente?) específico para a aplicação.Tentar reutilizar tal componente em uma aplicação diferente é contraproducente e só pode levar a um 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 não for esse o caso, aplicar DIP seria um projeto ruim.

A inversão de dependências bem aplicada proporciona flexibilidade e estabilidade ao nível de toda a arquitetura da sua aplicação.Isso permitirá que seu aplicativo evolua com mais segurança e estabilidade.

Arquitetura tradicional em camadas

Tradicionalmente, uma UI de arquitetura em camadas dependia da camada de negócios e esta, por sua vez, dependia da camada de acesso a dados.

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

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

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 da camada de biblioteca ou 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ências

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.

As abstrações não devem depender de detalhes.Os detalhes devem depender de abstrações.

Quais são os módulos de alto nível e de 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 de baixo nível dos quais dependem.

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

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

Imagine que adaptamos nosso código da seguinte forma:

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

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

}

E outra lógica de negócios da camada de biblioteca ou 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 negócios e acesso a dados permanece a mesma.

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

Para obter inversão de dependência, a interface de persistência deve ser definida no módulo ou pacote onde está esta 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 é definida como persistência.

// Domain.dll
public interface IProductRepository;

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

Após a camada de persistência depender do domínio, conseguindo inverter agora caso seja definida uma dependência.

// 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 de 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 alterar o tipo de persistência, seja o banco de dados ou a tecnologia para acessar o mesmo banco de dados, do que a lógica do domínio ou ações projetadas para se comunicar com a persistência.Por conta disso, a dependência é invertida pois é mais fácil alterar a persistência caso essa alteração ocorra.Desta forma não teremos que alterar o domínio.A camada de domínio é a mais estável de todas, por isso 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ências é fundamental para sua definição.Em todos os domínios é o mais importante e são as abstrações que indicarão o protocolo de comunicação entre o domínio e os demais pacotes ou bibliotecas definidos.

Arquitetura Limpa

Em Arquitetura limpa o domínio está localizado no centro e se você olhar na direção das setas que indicam 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 as portas são abstrações de comunicação do dominó para fora.Aqui novamente fica evidente que o domínio é o mais estável e a dependência tradicional é invertida.

Basicamente diz:

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

Uma maneira muito mais clara de afirmar 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 lógica de negócios.Em vez disso, eles deveriam depender apenas de interfaces para dados simples.

Ou seja, em vez de implementar sua classe Logic como as pessoas costumam fazer:

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

você deve 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 deve residir 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 agora estão dissociados.Quando Dependency mudanças, você não precisa mudar Logic.
  2. Entendendo o que Logic faz é uma tarefa muito mais simples:ele opera apenas no que parece ser um ADT.
  3. Logic agora pode ser testado mais facilmente.Agora você pode instanciar diretamente Data com dados falsos e passá-los.Não há necessidade de simulações ou andaimes de testes complexos.

Boas respostas e bons exemplos já foram dados por outros aqui.

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

Os objetos em seu software NÃO devem entrar em uma hierarquia onde alguns objetos sejam de nível superior, dependentes de objetos de baixo nível.As alterações nos objetos de baixo nível serão refletidas nos objetos de nível superior, o que torna o software muito frágil para alterações.

Você deseja que seus objetos de 'nível superior' sejam muito estáveis ​​e não frágeis para alterações; portanto, é necessário inverter as dependências.

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

Exemplo de pseudocódigo usando 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 COI 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 de unidade que substituem as dependências por versões simuladas.
  • Desacoplando o código.

O objetivo da inversão de dependência é tornar o software reutilizável.

A ideia é que, em vez de dois pedaços de código dependerem um do outro, eles dependam de alguma interface abstrata.Então você pode reutilizar qualquer uma das peças sem a outra.

A maneira mais comum de conseguir isso é por meio de um contêiner de inversão de controle (IoC) como Spring em Java.Neste modelo, as propriedades dos objetos são configuradas por meio de uma configuração XML em vez dos 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, MyClass depende de uma única interface, a interface IService.Deixaríamos o contêiner IoC definir o valor dessa variável.

Portanto, agora MyClass pode ser facilmente reutilizado em outros projetos, sem trazer consigo a dependência dessas outras duas classes.

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

Inversão de Contêineres de Controle e Padrão de Injeção de Dependência de Martin Fowler também é uma boa leitura.eu encontrei Use a cabeça primeiro, padrões de design um livro incrível para minha primeira incursão no aprendizado de DI e outros padrões.

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

Inversão de controle:Principal vs Abstração, e como o Principal é a cola dos sistemas.

DIP and IoC

Estas são algumas boas postagens 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/

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.Os 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();
        }
    }

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

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top