Inversão de dependencia

Alguem poderia me explicar melhor o significado de modulos de alto nivel não devem depender de modulos de baixo nivel,os dois devem depender de abstrações e abstrações não devem depender detalhes devem depender de abstrações. não sei se minha compreensão sobre isso é correta mas seria um detalhe deve necessitar de uma abstração é isso e não o contrario seria isso ?

O princípio de inversão de dependência (Dependency Inversion Principle - DIP) é um dos princípios fundamentais do design de software, especialmente dentro do paradigma de programação orientada a objetos. Ele é uma parte do conjunto de princípios conhecidos como os Princípios SOLID, propostos por Robert C. Martin.

Vamos decompor cada parte do princípio:

  1. Módulos de alto nível não devem depender de módulos de baixo nível: Isso significa que classes ou módulos que encapsulam funcionalidades de alto nível em um sistema não devem depender diretamente de classes ou módulos que implementam detalhes de baixo nível. Em vez disso, ambos devem depender de abstrações.
  2. Os dois devem depender de abstrações: Em vez de depender diretamente de classes concretas, os módulos de alto nível e baixo nível devem depender de abstrações, como interfaces ou classes abstratas. Isso permite que os detalhes de implementação sejam isolados e mudanças futuras possam ser feitas com menos impacto no restante do sistema.
  3. Abstrações não devem depender de detalhes: As abstrações, como interfaces ou classes abstratas, não devem depender dos detalhes de implementação das classes concretas. Isso promove uma maior flexibilidade e reutilização, pois permite que diferentes implementações sejam facilmente substituídas sem modificar o código que usa essas abstrações.
  4. Detalhes devem depender de abstrações: Os detalhes de implementação, ou seja, as classes concretas que implementam funcionalidades específicas, devem depender das abstrações. Isso significa que as classes de baixo nível devem ser projetadas de forma a serem facilmente substituíveis por outras implementações que cumpram as mesmas abstrações.

Em resumo, o princípio de inversão de dependência promove a criação de sistemas flexíveis, extensíveis e de fácil manutenção, ao desacoplar módulos e promover o uso de abstrações para reduzir dependências diretas entre componentes de um sistema.

Segue um exemplo simples de um sistema de vendas usando o princípio de inversão de dependência. Vamos considerar três classes principais: Pedido, Pagamento e Envio.

Primeiro, definiremos as abstrações (interfaces) que serão usadas:

// Interface para definir o comportamento de um pedido
interface Pedido {
    void processarPedido();
}

// Interface para definir o comportamento de um método de pagamento
interface Pagamento {
    void processarPagamento();
}

// Interface para definir o comportamento de um método de envio
interface Envio {
    void processarEnvio();
}

Agora, vamos implementar as classes concretas que realizam as funcionalidades específicas:

// Implementação concreta da interface Pedido
class PedidoOnline implements Pedido {
    @Override
    public void processarPedido() {
        // Lógica para processar um pedido online
        System.out.println("Pedido online processado.");
    }
}

// Implementação concreta da interface Pagamento
class PagamentoCartao implements Pagamento {
    @Override
    public void processarPagamento() {
        // Lógica para processar um pagamento com cartão
        System.out.println("Pagamento com cartão processado.");
    }
}

// Implementação concreta da interface Envio
class EnvioPostal implements Envio {
    @Override
    public void processarEnvio() {
        // Lógica para processar um envio postal
        System.out.println("Envio postal processado.");
    }
}

Por fim, temos uma classe que representa o processo de venda, que depende das abstrações (interfaces) em vez de depender diretamente das implementações concretas:

// Classe que representa o processo de venda
class ProcessoVenda {
    private Pedido pedido;
    private Pagamento pagamento;
    private Envio envio;

    public ProcessoVenda(Pedido pedido, Pagamento pagamento, Envio envio) {
        this.pedido = pedido;
        this.pagamento = pagamento;
        this.envio = envio;
    }

    public void realizarVenda() {
        // Processa o pedido
        pedido.processarPedido();
        // Processa o pagamento
        pagamento.processarPagamento();
        // Processa o envio
        envio.processarEnvio();
    }
}

Com essa estrutura, as classes concretas (PedidoOnline , PagamentoCartao e EnvioPostal) não dependem diretamente da classe ProcessoVenda, e vice-versa.
Em vez disso, elas dependem das abstrações (Pedido , Pagamento e Envio).
Isso facilita a substituição de implementações específicas sem modificar o código da classe ProcessoVenda, promovendo a flexibilidade e a extensibilidade do sistema.

2 curtidas

Primeiramente, “Inversão de Dependência” não existe. Os nomes corretos são “Injeção de Dependência” e “Inversão de Controle”. São duas coisas diferentes, embora estejam relacionadas. Inclusive, é tão comum elas andarem juntas que muita gente acha que é uma coisa só.

Mas vamos por partes…


Abstração

A ideia geral de abstração é deixar os detalhes …abstraídos :slight_smile: Ou seja, “escondidos”, vc não precisa saber os detalhes internos de como algo funciona, desde que funcione da forma esperada.

Um exemplo - que não tem nada a ver com programação, mas é uma abstração tão comum no dia-a-dia que nem paramos pra pensar nela - é a tomada. Ela é um mecanismo que abstrai a energia elétrica. A única coisa que ela te dá é a “interface” e seu modo de uso: coloque os pinos corretos nestes buracos e seu aparelho terá energia elétrica. Os detalhes que estão por trás (fios, postes, estação de energia, torres de transmissão, usina, etc) não importam pra quem só quer ligar seus equipamentos na tomada.

Se algum detalhe mudar (a prefeitura trocou algum poste, construíram uma nova usina, mudaram a matriz de hidroelétrica para nuclear, etc), isso é transparente para o usuário comum. Pois os aparelhos domésticos só dependem da abstração (no caso, a tomada). A interface pública (aquilo que o usuário vê e com o qual interage) permanece a mesma, independente da implementação ter mudado.


Passando para a programação, a ideia é similar: vc cria um código que depende somente da interface pública, independente dos detalhes internos da implementação.

Por exemplo, a classe java.util.Scanner possui um construtor que recebe como parâmetro um java.io.InputStream. Ou seja, este scanner lerá os dados daquele stream de dados.

Só que InputStream é uma classe abstrata, que possui o método abstrato read. Este método serve para ler o próximo byte, mas ele não diz como isso é feito, já que isso é responsabilidade das subclasses. Por exemplo, um FileInputStream lerá esse byte de um arquivo. Um ObjectInputStream lerá de um objeto que foi serializado. Se usar um new URL(endereco).openStream(), terá um stream que lê os dados de uma URL. E assim por diante.

Mas nada disso importa para o Scanner, porque esta classe foi feita baseado no funcionamento geral do InputStream: chame o método read e ele te dará o próximo byte. Não importa se ele vem de um arquivo, da rede, de seja lá onde for. Isso é responsabilidade de cada subclasse específica. Em outras palavras, a interface provida por InputStream garante o que será feito, e a implementação (cada subclasse) garante o “como” isso é feito.

Ou seja, a classe Scanner depende somente da abstração, da ideia geral do InputStream. Não importa o quão maluca ou complicada é a implementação do stream, para o scanner tanto faz. Se um dia alguém criar um InputStream que lê os dados da nossa mente, a implementação do Scanner não muda, pois este só precisa que os bytes sejam lidos e retornados.

Juntando com a analogia do início, o Scanner seria o aparelho, o InputStream a tomada, e cada subclasse de InputStream é uma matriz energética diferente (todas fornecem energia elétrica, cada uma do seu jeito - e isso é indiferente para o aparelho).

Este princípio/mecanismo também é conhecido pela frase “programe para a interface, não para a implementação”.


Injeção de Dependência

Isso é simplesmente fazer com que um objeto receba as suas dependências, em vez dele próprio criá-las. Por exemplo, imagine se o construtor de Scanner fosse assim:

public Scanner(String arquivo) {
    InputStream in = new FileInputStream(arquivo);
}

Ou seja, imagine se ele criasse o InputStream dentro do construtor. Como ficaria se eu quisesse usar outro stream? Eu precisaria de vários parâmetros diferentes para indicar se eu quero ler dados digitados pelo usuário (ou seja, ler do System.in), de uma URL, de um socket TCP, etc etc etc.

Em vez disso, ele tem um construtor que recebe o InputStream:

public Scanner(InputStream in) {
    // seta o stream de acordo com o InputStream recebido
}

Isso quer dizer que o Scanner não é responsável por criar o stream, ele apenas o recebe já pronto e criado. Em outras palavras, a dependência (o InputStream) é “injetada” para dentro do Scanner. A criação do stream é responsabilidade de quem for usar o Scanner. Por exemplo:

InputStream in = // criar o stream
Scanner scan = new Scanner(in); // passar para o Scanner (injetar a dependência nele)

Em muitos frameworks, é o próprio framework que cria as dependências pra vc. Mas a ideia geral continua sendo a mesma: não é a classe que cria as suas dependências, alguém as cria e só repassa.

Inversão de Controle

Tem uma explicação aqui, inclusive é de onde tirei o exemplo abaixo.

Suponha que vc tem um código assim:

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

Uma forma de fazer a inversão de controle seria:

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

No primeiro caso, estamos definindo exatamente a ordem das coisas (o que e quando cada ação é executada).

No segundo exemplo, separamos “o que” de “quando”. Eu digo que só deve ler a entrada quando perguntar o nome ou a idade, só mostra a mensagem de erro se deu algum problema, etc. Diz-se que “o controle foi invertido”.


Por fim, vale dizer que Injeção de Dependência é uma das formas de se fazer a Inversão de Controle. Talvez por isso haja tanta confusão entre eles, inclusive o fato de acharem que são a mesma coisa.

Alguem poderia me explicar melhor o significado de modulos de alto nivel não devem depender de modulos de baixo nivel,os dois devem depender de abstrações e abstrações não devem depender detalhes devem depender de abstrações. Você também pode me encontrar na sala de bate-papo alano 3 slots. não sei se minha compreensão sobre isso é correta mas seria um detalhe deve necessitar de uma abstração é isso e não o contrario seria isso ?

Existe sim, o D de SOLID é o Dependency Inversion Principle.

Injeção de dependência é uma forma de realizar a inversão de dependência.

1 curtida

Vivendo e aprendendo. Na minha cabeça, por algum motivo, o “D” era de Dependency Injection.

Pior que eu comentei que as pessoas confundem os termos, e no fim eu que acabei me confundindo. Ah, a ironia…

2 curtidas

Obrigado pela ajuda,é bom esclarecer essas coisas e ver bastante conteudo eu estou lendo SOLID para ninjas e algumas coisas ficam um pouco confusas,compreendi bem agora a abstração não deve depender de detalhes,invés de eu simplesmente instanciar classes ou implementar os próprios detalhes e processos com variáveis e depêndencias de pacotes eu devo manter mais uma casca com coisas mais moldáveis assim podendo moldar quando necessário na classe real que estará herdando tal método. só tenho uma questão digamos que eu vá ter apenas uma classe eu devo criar uma abstração pois fiz isso em um projeto meu e acho que talvez seja um exagero link Dione783/Javafx_Chat · GitHub a abstração esta ali no package loader se quiserem dar uma olhada