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


178

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



3
Quantidade ridícula de respostas aqui usando os termos "alto nível" e "baixo nível" da Wikipedia. Esses termos são inacessíveis e levam muitos leitores a esta página. Se você pretende regurgitar a Wikipedia, defina estes termos em suas respostas para dar contexto!
8bitjunkie 6/05/19

Respostas:


107

Confira este documento: O princípio da inversão de dependência .

Diz basicamente:

  • 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 isso é importante, resumindo: as mudanças são arriscadas e, dependendo de um conceito em vez de de uma implementação, você reduz a necessidade de alterações nos sites de chamada.

Efetivamente, o DIP reduz o acoplamento entre diferentes partes do código. A idéia é que, embora existam muitas maneiras de implementar, digamos, uma instalação de registro, a maneira como você a utilizaria deve ser relativamente estável no tempo. Se você pode 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ê pode fazer enquanto mantém ou estende esse mecanismo de 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 pode ser interessante também.


31
Esta resposta não diz por que o DIP é importante ou mesmo o que é DIP. Eu li o documento oficial do DIP e acho que esse é realmente um "princípio" pobre e injustificado, porque se baseia em uma suposição falha: que os módulos de alto nível são reutilizáveis.
Rogério

3
Considere um gráfico de dependência para alguns objetos. Aplique DIP aos objetos. Agora, qualquer objeto será independente da implementação dos outros objetos. O teste de unidade agora é simples. A refatoração posterior para reutilização é possível. As alterações de design têm escopos de alteração muito limitados. Os problemas de design não se conectam em cascata. Veja também o padrão de AI "Blackboard" para inversão de dependaecy de dados. Juntas, ferramentas muito poderosas para tornar o software compreensível, sustentável e confiável. Ignore a injeção de dependência neste contexto. Não está relacionado.
Tim Williscroft 30/08/09

6
Uma classe A usando uma classe B não significa que "depende" da implementação de B; depende apenas da interface pública de B. Adicionar uma abstração separada da qual A e B agora dependem apenas significa que A não terá mais uma dependência em tempo de compilação da interface pública de B. O isolamento entre unidades pode ser alcançado facilmente sem essas abstrações extras; existem ferramentas de simulação específicas para isso, em Java e .NET, que lidam com todas as situações (métodos estáticos, construtores, etc.). A aplicação do DIP tende a tornar o software mais complexo, menos sustentável e não mais testável.
31411 Rogério

1
Então, o que é inversão de controle? uma maneira de obter a inversão de dependência?
user20358

2
@ Rogerio, veja a resposta de Derek Greer abaixo, ele explica. AFAIK, DIP diz que é A quem manda o que A precisa, e não B quem diz o que A precisa. Portanto, a interface que A precisa não deve ser dada por B, mas por A.
ejaenv 13/12/12

145

Os livros Desenvolvimento Ágil de Software, Princípios, Padrões e Práticas e Princípios Ágeis, Padrões e Práticas em C # são os melhores recursos para entender completamente os objetivos e motivações originais por trás do Princípio da Inversão da Dependência. O artigo "O Princípio da Inversão da Dependência" também é um bom recurso, mas, devido ao fato de ser uma versão condensada de um rascunho que acabou entrando nos livros mencionados anteriormente, deixa de lado uma importante discussão sobre o conceito de propriedade de pacote e interface, que é essencial para distinguir esse princípio do conselho mais geral de "programar para uma interface, não para uma implementação" encontrada no livro Design Patterns (Gamma, et. al).

Para fornecer um resumo, o Princípio de Inversão de Dependência tem como principal objetivo reverter a direção convencional das 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 pertencentes aos componentes de "nível superior" . (Nota: o componente "nível superior" aqui refere-se 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 é desviado dos componentes que são teoricamente menos valioso para componentes que são teoricamente mais valiosos.

Isso é obtido 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ê o usa (por exemplo, "INeedSomething", não "IDoSomething").

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

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

Dentro desse 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 de DI e deseja fornecer log, mas não deseja acoplar seu contêiner a um logger específico, de modo que todos que usam seu contêiner também use a biblioteca de log escolhida).

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

Com o primeiro caso de reutilização de componentes em vários aplicativos, como em 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 a dependência de tais dependências exige que você consumidores exigirem as mesmas dependências também. Isso pode ser problemático quando os consumidores da sua biblioteca optarem 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 a versão anterior. exigido pela sua biblioteca.

Com o segundo caso de reutilização de componentes da 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 em constante mudança dos detalhes da sua implementação (por exemplo, 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.

Nota: Alguns podem se opor a descrever esse 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 idéia aqui, no entanto, é que cada alteração nos detalhes de implementação do aplicativo renderize um novo contexto e, portanto, um caso de uso diferente, embora os objetivos finais possam ser distinguidos como isolamento versus portabilidade.

Embora seguir o Princípio da inversão de dependência neste segundo caso possa oferecer algum benefício, observe-se que seu valor aplicado a linguagens modernas como Java e C # é muito reduzido, talvez a ponto de ser irrelevante. Como discutido anteriormente, o DIP envolve a separação completa dos detalhes da 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 às necessidades variáveis ​​dos componentes de detalhes da implementação, mesmo que os detalhes da implementação acabem residindo no mesmo pacote . Essa 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 as linguagens mais recentes. Dito isto,

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


17
Obrigado. Agora vejo como minha resposta erra o alvo. A diferença entre MyService → [ILogger ⇐ Logger] e [MyService → IMyServiceLogger] ⇐ Logger é sutil, mas importante.
Patrick McElhaney

2
Na mesma linha, está muito bem explicado aqui: lostechies.com/derickbailey/2011/09/22/…
ejaenv 13/12/12

1
Como eu gostaria que as pessoas parassem de usar loggers como o caso canônico de injeção de dependência. Especialmente em conexão com o log4net, onde é quase um anti-padrão. Isso de lado, explicação chuta!
Casper Leon Nielsen

4
@ Casper Leon Nielsen - DIP não tem nada a ver com DI Eles não são sinônimos nem conceitos equivalentes.
tsmith

4
@ VF1 Conforme indicado no parágrafo sumário, a importância do Princípio de Inversão de Dependência é principalmente com a reutilização. Se John libera uma biblioteca que possui uma dependência externa em uma biblioteca de log e Sam deseja usar a biblioteca de John, Sam assume a dependência de log temporário. Sam nunca poderá implantar seu aplicativo sem a biblioteca de log que John selecionou. Se John seguir o DIP, no entanto, Sam estará livre para fornecer um adaptador e usar a biblioteca de logs que ele escolher. O DIP não tem a ver com conveniência, mas com acoplamento.
Derek Greer

14

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

Os últimos contam com classes de baixo nível. Uma maneira natural de implementar essas estruturas seria escrever classes de baixo nível e assim que as tivermos que escrever classes complexas de alto nível. Como as classes de alto nível são definidas em termos de outras, essa 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.
  • As abstrações não devem depender de detalhes. Os detalhes devem depender de abstrações.

Esse princípio procura "inverter" a noção convencional de que os módulos de alto nível no 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. Tornando assim os módulos de nível inferior dependentes dos módulos de nível superior.


11

A inversão de dependência bem aplicada oferece flexibilidade e estabilidade no nível de toda a arquitetura do seu aplicativo. Isso permitirá que seu aplicativo evolua com mais segurança e estabilidade.

Arquitetura em camadas tradicional

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

Você precisa entender a camada, o pacote ou a biblioteca. Vamos ver como seria 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ê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. Os 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, o módulo de alto nível seria aquele que tradicionalmente possui dependências e o baixo nível do qual eles 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 desse 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 adotada, podemos aplicar mal a dependência da dependência de investimentos, 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 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.

Para obter inversão de dependência, a interface de persistência deve ser definida no módulo ou pacote em que essa lógica ou domínio de alto nível está 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;
}

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

// Persistence.dll
public class ProductDAO : IProductRepository{

}


(fonte: xurxodev.com )

Aprofundando o princípio

É importante assimilar bem o conceito, aprofundando o objetivo e os benefícios. Se permanecermos mecanicamente e aprendermos o repositório de casos típico, não conseguiremos 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?

Isso geralmente 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, o banco de dados ou a tecnologia para acessar o mesmo banco de dados do que a lógica do domínio ou as ações projetadas para se comunicar com persistência. Por esse motivo, a dependência é revertida, pois é mais fácil alterar a persistência se essa alteração ocorrer. Dessa forma, não precisaremos alterar 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 há apenas este exemplo de repositório. Existem muitos cenários em que esse princípio se aplica e há arquiteturas baseadas nesse princípio.

Arquiteturas

Existem arquiteturas nas quais a inversão de dependência é essencial para sua definição. Em todos os domínios, é o mais importante e são as abstrações que indicam o protocolo de comunicação entre o domínio e o restante dos pacotes ou bibliotecas.

Arquitetura Limpa

Na arquitetura Limpa, 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.


(fonte: 8thlight.com )

Arquitetura hexagonal

Isso acontece da mesma maneira 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 o exterior. Aqui, novamente, é evidente que o domínio é o mais estável e a dependência tradicional é invertida.


Não tenho certeza do que se pretende dizer, mas esta frase é incoerente: "Mas, de acordo com a abordagem adotada, podemos aplicar mal a dependência da dependência de investimentos, mas uma abstração". Talvez você queira consertar?
Mark Amery

9

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

O problema no C ++ é que os arquivos de cabeçalho geralmente contêm declarações de campos e métodos particulares. 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á dos detalhes reais da implementação desse módulo. E isso, obviamente, não é uma coisa boa. Mas isso não é um problema nas línguas mais modernas usadas hoje em dia.

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

Portanto, 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) pode ser feita apenas se o componente A for realmente útil para reutilização em diferentes aplicativos ou contextos. Se não for esse o caso, aplicar o DIP seria um design ruim.


Mesmo que a abstração de alto nível seja útil apenas no contexto desse aplicativo, ela pode ter valor quando você deseja substituir a implementação real por um stub para teste (ou fornecer interface de linha de comando para um aplicativo geralmente disponível na Web)
Przemek Pokrywka

3
O DIP é importante entre os idiomas e não tem nada a ver com o próprio C ++. Mesmo que seu código de alto nível nunca saia do aplicativo, o DIP permite que você contenha alterações de código ao adicionar ou alterar o código de nível inferior. Isso reduz os custos de manutenção e as conseqüências não intencionais da mudança. DIP é um conceito de nível superior. Se você não entender, precisará pesquisar mais.
Dirk Bester

3
Eu acho que você não entendeu o que eu disse sobre C ++. Foi apenas uma motivação para o DIP; sem dúvida é mais geral que isso. Observe que o artigo oficial sobre o DIP deixa claro que a motivação central era apoiar a reutilização de módulos de alto nível, tornando-os imunes a alterações nos módulos de baixo nível; sem necessidade de reutilização, é muito provável que exista um excesso de engenharia e um excesso de engenharia. (Será que você lê-lo Ele também fala sobre a questão C ++?.)
Rogério

1
Você não está negligenciando a aplicação reversa do DIP? Ou seja, os módulos de nível superior implementam seu aplicativo, essencialmente. Sua principal preocupação não é reutilizá-lo, é torná-lo menos dependente das implementações de módulos de nível inferior, para que atualizá-lo para acompanhar as mudanças futuras isola seu aplicativo dos estragos do tempo e das novas tecnologias. Não é para facilitar a substituição do WindowsOS, mas para torná-lo menos dependente dos detalhes de implementação do FAT / HDD, para que os NTFS / SSDs mais recentes possam ser conectados ao WindowsOS com pouco ou nenhum impacto.
precisa saber é o seguinte

1
A tela da interface do usuário definitivamente não é o módulo de nível mais alto ou não deve ser para qualquer aplicativo. Os módulos de nível mais alto são os que contêm regras de negócios. Um módulo de nível mais alto também não é definido por seu potencial de reutilização. Verifique explicação simples do Tio Bob neste artigo: blog.cleancoder.com/uncle-bob/2016/01/04/...
Humbaba

8

Basicamente diz:

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


pode ser tão simples? Existem artigos longos e até livros como o DerekGreer mencionou? Na verdade, eu estava procurando por uma resposta simples, mas é inacreditável se é assim tão simples: D
Darius.V

1
@ darius-v não é outra versão do ditado "use abstractions". É sobre quem possui as interfaces. O diretor diz que o cliente (componentes de nível superior) deve definir interfaces e os componentes de nível inferior devem implementá-las.
boran

6

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

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

Os objetos no seu software NÃO devem entrar em uma hierarquia onde alguns são os de nível superior, dependentes de objetos de nível inferior. As alterações nos objetos de nível inferior serão propagadas para os 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 sejam frágeis para alterações; portanto, é necessário inverter as dependências.


6
Como exatamente o DIP faz isso, para código Java ou .NET? Nessas linguagens, ao contrário do C ++, as alterações na implementação de um módulo de baixo nível não exigem alterações nos módulos de alto nível que o utilizam. Somente as mudanças na interface pública se propagariam, mas a abstração definida no nível superior também teria que mudar.
Rogério

5

Uma maneira muito mais clara de declarar o Princípio da Inversão da 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 Logiccomo 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
    }
}

Datae DataFromDependencydeve viver 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 Dependencymuda, você não precisa mudar Logic.
  2. Entender o que Logicfaz é uma tarefa muito mais simples: ela opera apenas no que parece um ADT.
  3. Logicagora pode ser mais facilmente testado. Agora você pode instanciar diretamente Datacom dados falsos e transmiti-los. Não há necessidade de zombarias ou andaimes de teste complexos.

Eu não acho que isso esteja certo. Se DataFromDependency, que referencia diretamente Dependency, estiver no mesmo módulo que Logic, então o Logicmódulo ainda depende diretamente do Dependencymódulo no momento da compilação. De acordo com a explicação do tio Bob sobre o princípio , evitar esse é o ponto principal do DIP. Em vez disso, para seguir o DIP, Datadeve estar no mesmo módulo que Logic, mas DataFromDependencydeve estar no mesmo módulo que Dependency.
Mark Amery

3

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 uma dependência de estrutura.

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 da 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, preferencialmente usando interfaces, é fácil criar testes de unidade que substituem dependências por versões simuladas.
  • Desacoplar código.

Princípio de inversão de dependência e inversão de controle são a mesma coisa?
22640 Peter Mortensen

3
A "inversão" no DIP não é a mesma que na Inversão de controle. O primeiro é sobre dependências em tempo de compilação, enquanto o segundo é sobre o fluxo de controle entre os métodos em tempo de execução.
Rogério

Eu sinto que a versão IoC está faltando alguma coisa. Como é que objeto de banco de dados ele é definido e de onde vem. Eu estou tentando entender como isso não apenas atrasar especificando todas as dependências para o nível superior em uma dependência deus gigante
Richard Tingle

1
Como já foi dito por Rogério, o DIP não é DI / IoC. Esta resposta está errada IMO
zeraDev

1

O ponto de inversão de dependência é criar software reutilizável.

A idéia é que, em vez de dois códigos confiando um no outro, eles confiem em alguma interface abstraída. Depois, você pode reutilizar uma das partes sem a outra.

A maneira mais comum de conseguir 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 dos dois 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. Permitimos que o contêiner de IoC realmente definisse o valor dessa variável.

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

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


2
Esta resposta realmente não é sobre DIP, mas outra coisa. Dois pedaços de código podem confiar em alguma interface abstraída e não uma na outra, e ainda não seguem o Princípio de Inversão de Dependência.
Rogério

1

Se considerarmos que um funcionário de "alto nível" de uma corporação é pago pela execução de seus planos e que esses planos são entregues pela execução agregada de muitos planos de funcionários de "baixo nível", poderíamos dizer geralmente é um plano terrível se a descrição do plano do funcionário de alto nível for associada de alguma forma ao plano específico de qualquer funcionário de nível inferior.

Se um executivo de alto nível tem um plano para "melhorar o tempo de entrega" e indica que um funcionário da linha de remessa deve tomar café e fazer alongamentos todas as manhãs, esse plano é altamente acoplado e tem baixa coesão. Mas se o plano não mencionar nenhum funcionário específico e, de fato, exigir apenas "uma entidade que pode executar o trabalho está preparada para trabalhar", então o plano será fracamente acoplado e mais coeso: os planos não se sobrepõem e podem ser facilmente substituídos . Os contratados, ou robôs, podem facilmente substituir os funcionários e o plano de alto nível permanece inalterado.

"Alto nível" no princípio de inversão de dependência significa "mais importante".


1
Essa é uma analogia notavelmente boa. Por mais que, no DIC, os componentes de alto nível ainda dependam em tempo de execução dos de baixo nível, nessa analogia, a execução dos planos de alto nível depende dos planos de baixo nível. Mas também, assim como no DIC são os planos de baixo nível que dependem em tempo de compilação dos planos de alto nível, é o seu caso em sua analogia que a formação dos planos de baixo nível depende dos planos de alto nível.
Mark Amery

0

Eu posso ver que uma boa explicação foi dada nas respostas acima. No entanto, eu quero fornecer algumas explicações fáceis com um exemplo simples.

O Princípio de Inversão de Dependência permite que o programador remova as dependências codificadas permanentemente para que o aplicativo se torne fracamente acoplado e extensível.

Como conseguir isso: através da abstração

Sem inversão de dependência:

 class Student {
    private Address address;

    public Student() {
        this.address = new Address();
    }
}
class Address{
    private String perminentAddress;
    private String currentAdrress;

    public Address() {
    }
} 

No trecho de código acima, o objeto de endereço é codificado. Em vez disso, se podemos usar a inversão de dependência e injetar o objeto de endereço passando pelo método construtor ou setter. Vamos ver.

Com inversão de dependência:

class Student{
    private Address address;

    public Student(Address address) {
        this.address = address;
    }
    //or
    public void setAddress(Address address) {
        this.address = address;
    }
}


-1

Digamos que temos duas classes: Engineere Programmer:

O engenheiro de classe depende da classe Programmer, como abaixo:

class Engineer () {

    fun startWork(programmer: Programmer){
        programmer.work()
    }
}

class Programmer {

    fun work(){
        //TODO Do some work here!
    }
}

Neste exemplo, a classe Engineerdepende da nossa Programmerclasse. O que acontecerá se eu precisar alterar o Programmer?

Obviamente, eu preciso mudar Engineertambém. (Uau, neste momento também OCPé violado)

Então, o que temos para limpar essa bagunça? A resposta é abstração, na verdade. Por abstração, podemos remover a dependência entre essas duas classes. Por exemplo, eu posso criar um Interfacepara a classe Programmer e, a partir de agora, toda classe que deseja usar Programmertem que usar a sua Interface. Em seguida, alterando a classe Programmer, não precisamos alterar nenhuma classe que a utilizou. usava.

Nota: DependencyInjectionpode nos ajudar a fazer DIPe SRPtambém.


-1

Além da enxurrada de respostas geralmente boas, gostaria de adicionar uma pequena amostra para demonstrar boas práticas versus práticas ruins. E sim, eu não sou de jogar pedras!

Digamos, você quer um pequeno programa para converter uma string no formato base64 via E / S do console. Aqui está a abordagem ingênua:

class Program
{
    static void Main(string[] args)
    {
        /*
         * BadEncoder: High-level class *contains* low-level I/O functionality.
         * Hence, you'll have to fiddle with BadEncoder whenever you want to change
         * the I/O mode or details. Not good. A good encoder should be I/O-agnostic --
         * problems with I/O shouldn't break the encoder!
         */
        BadEncoder.Run();            
    }
}

public static class BadEncoder
{
    public static void Run()
    {
        Console.WriteLine(Convert.ToBase64String(Encoding.UTF8.GetBytes(Console.ReadLine())));
    }
}    

O DIP basicamente diz que os componentes de alto nível não devem depender da implementação de baixo nível, onde "nível" é a distância da E / S, de acordo com Robert C. Martin ("Arquitetura limpa"). Mas como você sai dessa situação difícil? Simplesmente tornando o codificador central dependente apenas de interfaces, sem se preocupar em como elas são implementadas:

class Program
{
    static void Main(string[] args)
    {           
        /* Demo of the Dependency Inversion Principle (= "High-level functionality
         * should not depend upon low-level implementations"): 
         * You can easily implement new I/O methods like
         * ConsoleReader, ConsoleWriter without ever touching the high-level
         * Encoder class!!!
         */            
        GoodEncoder.Run(new ConsoleReader(), new ConsoleWriter());        }
}

public static class GoodEncoder
{
    public static void Run(IReadable input, IWriteable output)
    {
        output.WriteOutput(Convert.ToBase64String(Encoding.ASCII.GetBytes(input.ReadInput())));
    }
}

public interface IReadable
{
    string ReadInput();
}

public interface IWriteable
{
    void WriteOutput(string txt);
}

public class ConsoleReader : IReadable
{
    public string ReadInput()
    {
        return Console.ReadLine();
    }
}

public class ConsoleWriter : IWriteable
{
    public void WriteOutput(string txt)
    {
        Console.WriteLine(txt);
    }
}

Observe que você não precisa tocar GoodEncoderpara alterar o modo de E / S - essa classe está satisfeita com as interfaces de E / S que conhece; qualquer implementação de baixo nível IReadablee IWriteablenunca a incomodará.


Eu não acho que isso seja inversão de dependência. Afinal, você não inverteu nenhuma dependência aqui; seu leitor e escritor não dependem do GoodEncoderseu segundo exemplo. Para criar um exemplo DIP, é necessário introduzir uma noção do que "possui" as interfaces que você extraiu aqui - e, em particular, colocá-las no mesmo pacote que o GoodEncoder enquanto suas implementações permanecem fora.
Mark Amery

-2

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

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

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.