Primeiras aulas do curso SOLID com Java: Orientação a Objetos com Java

SOLID com Java: Orientação a Objetos com Java

Coesão e o Single Responsibility Principle - Coesão e o Single Responsibility Principle

Olá! Bem-vindo ao curso de Orientação a objetos avançado e SOLID do Alura. Neste curso eu vou discutir com vocês todas aquelas ideias de Orientação a objetos, como coesão, acoplamento, encapsulamento etc., só que dessa vez com um ponto de vista um pouquinho mais avançado, um pouquinho mais profundo nesses assuntos.

Vou discutir com vocês como conseguir fazer classes que são bastante coesas, que são pouco acopladas, como evitar fazer classes e métodos que não estão bem encapsulados etc. e tal. Pra esse curso, eu espero que você conheça um pouquinho de Orientação a objetos, já tenha alguma prática com alguma linguagem que dá suporte. Aqui no curso, vou usar Java, mas tudo o que eu discutir vai funcionar para todas as linguagens que são OO. Tá legal?

A primeira aula vai ser sobre coesão. Todo mundo já ouviu falar de coesão, certo? E tem aquela máxima lá, “Olha, todas as suas classes, elas têm que ser sempre muito coesas.” Agora a pergunta é: como fazer isso? Como criar classes que são coesas o tempo inteiro? Como evitar criar classes que tenham baixa coesão?

E para discutir isso, eu vou mostrar um código que parece código do mundo real. Dá uma olhada. Eu tenho aqui a minha classe CalculadoraDeSalario. Essa classe, dá para ver pelo método calcula, tem por objetivo calcular o salário de um funcionário. Dado um funcionário, e um cargo que esse funcionário tem, eu tenho uma regra diferente do cálculo do salário dele. Dá uma olhada:


public class CalculadoraDeSalario {


    public double calcula(Funcionario funcionario) {
        if(DESENVOLVEDOR.equals(funcionario.getCargo())) {
            return dezOuVintePorcento(funcionario);
        }

        if(DBA.equals(funcionario.getCargo()) || TESTER.equals(funcionario.getCargo())) {
            return quinzeOuVinteCincoPorcento(funcionario);
        }

        throw new RuntimeException("funcionario invalido");
    }

    private double dezOuVintePorcento(Funcionario funcionario) {
        if(funcionario.getSalarioBase() > 3000.0) {
            return funcionario.getSalarioBase() * 0.8;
        }
        else {
            return funcionario.getSalarioBase() * 0.9;
        }
    }

    private double quinzeOuVinteCincoPorcento(Funcionario funcionario) {
        if(funcionario.getSalarioBase() > 2000.0) {
            return funcionario.getSalarioBase() * 0.75;
        }
        else {
            return funcionario.getSalarioBase() * 0.85;
        }
    }

}

Se o cara for desenvolvedor, ele cai nesse método aí 10% ou 20%. Esse método lá dentro verifica se o salário dele é maior do que tanto, e aí ele dá um desconto. Caso contrário (else), ele dá um outro desconto. A mesma coisa acontece para DBA e TESTER. Veja que ambos compartilham a mesma regra, essa regra de 15% ou 25%. E ali, a implementação é mais ou menos a mesma.

Essa é uma classe que parece bastante com código do mundo real. Certo? Onde eu tenho classes que olham o estado de um objeto e, a partir daí, decidem qual algoritmo executar, e fazem isso por meio de ifs e fors e whiles etc. e tal.

Agora a pergunta é: qual é o problema desse código? Será que ele é um código coeso? Será que ele está muito acoplado? Vamos lá.

Voltando aqui para ele. Esse código, ele não é lá muito coeso, está certo? Como que eu sei disso? A minha primeira dica é o seguinte, eu sempre paro e penso: “Puxa, será que essa classe vai parar de crescer? Porque veja só: se a classe é coesa, ou seja, ela tem uma única responsabilidade, ela cuida de apenas uma única parte do meu sistema, ela representa uma única entidade. Se essa classe é coesa, ela tem que parar de crescer um dia. Porque as responsabilidades de uma entidade um dia param de aparecer.

Nesse caso, em particular, não. Veja só. Sempre que eu criar um cargo novo no meu sistema, eu vou ter que alterar essa classe. Sempre, sempre, sempre. Agora imagina no mundo real, muito mais complexo do que esse código, onde eu tenho, sei lá, 30, 40, 50 cargos. E imagina que eu tenha 30 regras diferentes. O que vai acontecer com essa classe? Essa classe vai ficar gigante, ela vai ter 30 regras diferentes. Tá certo? Então, ela não vai ser nada coesa.

Qual é a consequência disso? Um código complicado. Todo mundo sabe a consequência de um código complicado, certo? É mais difícil de manter, é muito mais suscetível a um bug, porque o código é enorme, você vai deixar passar alguma coisa ali, o reuso é muito menor, porque se a classe faz muita coisa é difícil levá-la pra um outro sistema. Porque o outro sistema raramente vai precisar de tudo o que ela faz. E assim por diante.

Veja só, pessoal, que complexidade, um código complicado, ele é bastante discutível no mundo OO. Porque às vezes eu posso ter um código complicado, mas dentro de uma classe coesa. E esse tipo de código me incomoda menos. Porque se eu tenho um método público, cujo código está feio, é fácil: eu jogo a implementação dele fora e escrevo de novo. Tem um monte de técnica de refatoração pra isso. Escrever variáveis com bons nomes, criar métodos privados, extrair para variáveis locais que explicam melhor o que o algoritmo está fazendo etc. e tal. Eu nem vou entrar nesse mérito de refatoração, porque é uma outra discussão e você pode até fazer o nosso curso sobre o assunto (o curso de refatoração).

Mas aqui, o que me incomoda é que essa classe, além de complicada, ela não é coesa. Ela vai crescer pra sempre. Então, ela vai dificultar e muito a vida do desenvolvedor.

Veja o sistema hoje. Eu tenho uma CalculadoraDeSalario, que tem as várias regras de cálculo. E toda vez que surgir uma regra nova, eu vou ter que enfiá-la aí dentro. Esse tipo de código, galera, ainda dificulta a testabilidade. Se eu estou pensando em teste automatizado, o teste dessa classe vai ser muito complicado. Eu vou ter que escrever uma bateria imensa pra ela. Difícil, certo? Qual é a ideia?

O que eu estou tentando fazer agora é pegar cada uma das regras que eu tinha de cálculo ? DezOuVintePorCento, QuinzeOuVintePorCento, TrintaOuQuarentaPorCento, sei lá, as regras que forem aparecendo -, e colocar cada uma dessas regras num único lugar.

Veja só o que ia acontecer na prática. Vou voltar aqui pro meu código. Eu tenho os dois métodos privados, e imagina que cada um desses métodos privados eu vá levar para uma classe em particular. Eu vou extrair um método privado e colocar numa classe. O que eu vou ganhar? Veja só que cada uma dessas regras, cada uma dessas classes, será coesa. Porque eu vou ter uma classe cuja única responsabilidade é entender da regra de DezOuVintePorCento, a outra, de QuinzeOuVinteCincoPorCento.

Ou seja, cada classe vai entender de uma única regra. Se a regra mudar, tudo bem, eu vou lá e mexo na classe. Mas veja só que ela vai mudar por um único motivo. O motivo é se essa regra em particular mudar. Se uma regra nova aparecer, eu não vou nem precisar abrir a classe DezOuQuinzePorCento ou QuinzeOuVinteCincoPorCento, não vou. Vou simplesmente criar uma nova classe.

Aqui estou criando classes coesas. E como eu optei por tratar a coesão nesse código? Eu, com a minha experiência, percebi que o que muda de um pro outro é basicamente a regra que é aplicada em cada um dos casos. Tá certo?

Eu peguei a regra e coloquei em classes menores. Essas classes serão mais coesas. Excelente. Eu vou mostrar isso em código agora pra vocês, fazendo essa refatoração, pra vocês verem como isso vai ficar muito mais legal.

Agora no Eclipse, vamos lá. A primeira coisa que eu vou fazer é pegar cada um desses métodos privados e extrair pra uma classe. Então veja, vou criar a classe que eu vou chamar de DezOuVintePorCento.

E vou pegar esse método privado aqui:


private double dezOuVintePorcento(Funcionario funcionario) {
        if(funcionario.getSalarioBase() > 3000.0) {
            return funcionario.getSalarioBase() * 0.8;
        }
        else {
            return funcionario.getSalarioBase() * 0.9;
        }
    }

E vou jogar pra lá. Por enquanto deixa assim, está meio estranho mas tudo bem. Vou fazer a mesma coisa com o QuinzeOuVinteECincoPorCento. Nova classe:


   private double quinzeOuVinteCincoPorcento(Funcionario funcionario) {
        if(funcionario.getSalarioBase() > 2000.0) {
            return funcionario.getSalarioBase() * 0.75;
        }
        else {
            return funcionario.getSalarioBase() * 0.85;
        }
    }

}

Legal. A primeira coisa que eu já percebi. É que veja só que tem duas coisas em comum entre essas duas classes que eu acabei de criar, certo? Ambas devolvem um double e recebem um Funcionario, e ambas são uma regra de cálculo. No mundo orientado a objetos, a gente vai criar interfaces, certo? É sempre legal programar pra interfaces.

No próximo capítulo sobre acoplamento, eu vou discutir a importância de interfaces. Elas são estáveis, etc. e tal. Mas eu chego lá.

Aqui por enquanto eu vou só criar a interface, então uma RegraDeCalculo, método public double calcula que vai receber um funcionário aqui, que eu vou colocar funcionario:

public double calcula(Funcionario funcionario);

Agora eu vou nessas duas regras de negócio que eu tenho, e vou implementar, implements RegraDeCalculo. Vou mudar o método aqui para calcula, o método fica public, certo? Legal.

public class DezOuVintePorCento implements RegraDeCalculo {

    public double calcula(Funcionario funcionário) {
            if(funcionario.getSalarioBase() > 3000.0) {
                return funcionario.getSalarioBase() * 0.8;
        }
        else {
            return funcionario.getSalarioBase() * 0.9;
        }
 }
}

A mesma coisa no QuinzeOuVinteECincoPorCento:

public class QuinzeOuVinteECincoPorCento implements RegraDeCalculo  {
    public double calcula(Funcionario funcionario) {
        if(funcionario.getSalarioBase() > 2000.0) {
            return funcionario.getSalarioBase() * 0.75;
        }
        else {
            return funcionario.getSalarioBase() * 0.85;
        }
    }

}

Excelente. Veja só. As duas classes que eu criei com as regras de negócio são muito mais coesas. Essa aqui só tem a regra de QuinzeOuVinteECincoPorCento, e essa aqui só tem a de DezOuVintePorCento. Ótimo!

Veja só que essa classe – as duas, na verdade – não sofrem o problema da classe antiga da CalculadoraDeSalario, porque essa classe não vai crescer pra sempre. A única responsabilidade dela é cuidar dessa regra de DezOuVintePorCento, e dessa outra, de QuinzeOuVinteECincoPorCento, e assim por diante.

Agora vou voltar aqui para a CalculadoraDeSalario e vamos fazer a refatoração mais simples, que é dar um new aqui na regra. DezOuVintePorCento(). calcula e passo o funcionario. A mesma coisa aqui no debaixo, return new QuinzeOuVinteECincoPorCento().calcula(funcionario);:

public class CalculadoraDeSalario {


    public double calcula(Funcionario funcionario) {
        if(DESENVOLVEDOR.equals(funcionario.getCargo())) {
            return new DezOuVintePorCento(). calcula(funcionario);
        }

        if(DBA.equals(funcionario.getCargo()) || TESTER.equals(funcionario.getCargo())) {
            return new QuinzeOuVinteECincoPorCento().calcula(funcionario);
        }

        throw new RuntimeException("funcionario invalido");
    }

Ótimo, já está melhor, certo? Esse nosso código já está bem melhor. Todas as classes são mais ou menos coesas, mais ou menos por que essas duas estão bem coesas, mas a CalculadoraDeSalario está meio estranha ainda. Essa aqui ainda não para de crescer, certo, sempre que um cargo novo aparecer, eu vou ter que lembrar de colocar um if a mais aqui.

Vamos resolver isso aqui. Como que eu vou fazer? Fácil. Todo cargo tem uma regra de negócio associada, certo? Então é isso que eu vou fazer aqui. Quando eu criar um cargo, eu vou passar pra ele o tipo de regra de negócio que ele vai usar. DESENVOLVEDOR(new DezOuVintePorCento()),, o DBA(new QuinzeOuVinteECincoPorCento()),, e a mesma coisa para o meu TESTER(new QuinzeOuVinteECincoPorCento());:

public enum Cargo {
    DESENVOLVEDOR(new DezOuVintePorCento()),
    DBA(new DezOuVintePorCento()),
    TESTER(new QuinzeOuVinteECincoPorCento());
}

Está certo? As pessoas esquecem – esse é um detalhe do Java – que o enum é quase uma classe, eu posso ter código nele.

O que eu vou fazer aqui, vou criar o construtor do Cargo, que vai receber uma RegraDeCalculo, que vou chamar de regra, vou guardar essa regra num atributo do enum, e vou criar aqui o getRegra:

private RegraDeCalculo regra;

Cargo(RegraDeCalculo regra)  {
    this.regra = regra;
}
public RegraDeCalculo getRegra()  {
    return regra;
}

Por que que eu fiz isso? Porque, veja só, com o enum desse jeito, se eu criar um cargo novo, igual, por exemplo SECRETARIO, se eu fizer isso aqui, o código não compila. Ele vai, obrigatoriamente, me pedir o quê? Uma regra de cálculo. Está certo?

Então, vamos lá. Nosso código está funcionando e eu vou voltar aqui para a CalculadoraDeSalario. Aqui, veja só como ficou mais fácil, dá pra jogar tudo isso aqui fora e fazer simplesmente return funcionario.getCargo().getRegra().calcula(funcionario);:

public double calcula(Funcionario funcionario)  {
    return funcionario.getCargo().getRegra().calcula(funcionario);`

Eu poderia até, na verdade, esconder isso aqui dentro do próprio funcionario, alguma coisa como:

public double  calcula(Funcionario funcionario)  {
     return funcionario.calculaSalario();
}

E esse método lá dentro vai fazer simplesmente:

public double calculaSalario()  {
    return  cargo.getRegra().calcula(this);
} 

Calcula para ele mesmo (this). Tudo isso aqui continua funcionando. Apaguei os imports ali em cima e o código ficou menor.

Nessa minha implementação, talvez essa classe CalculadoraDeSalario passe até a ser inútil, porque agora ela só tem uma única linha de código, calculaSalario(). O meu grande segredo aqui foi as classes DezOuVintePorCento, QuinzeOuVinteECincoPorCento.

Dá uma olhada também na classe Funcionario. A minha classe Funcionario é bem simples, ela tem um monte de atributos, um deles é o cargo, e aqui o calculaSalario. Essa classe é coesa, até porque não tem muito por que ela não ser, certo? Ela só tem regras de Funcionario. Não tem problema.

O problema de coesão que eu dei pra vocês nesse exercício foi justamente naquela CalculadoraDeSalario que fazia muita coisa, e a solução foi espalhar as regras em classes diferentes, e fazer o Cargo ser mais inteligente. Certo?

Já vou explicar também a vantagem dessa refatoração aqui do Cargo, que não envolve só coesão. Já chego lá.

Ótimo, nosso código ficou muito melhor. Mas agora eu quero dar uma olhada no código antigo de novo, porque na nossa refatoração, nós resolvemos dois problemas, na verdade, que essa classe tinha. Um deles era de coesão, que eu discuti com vocês, mas o segundo eu passei batido, porque eu não queria entrar em detalhe agora. Mas olha só, um outro grande problema de códigos orientados a objeto é a quantidade de pontos que eu tenho que mudar, dada uma alteração do meu usuário.

Veja só. Nesse código antigo, da maneira com que estava programado antes, se eu criasse um cargo novo, por exemplo, o cargo de SECRETARIO, o que eu ia ter que fazer? Eu ia ter que mexer no meu enum, certo? Ia ter que adicionar uma linha a mais ali no meu enum, só que, mais do que isso, eu ia ter que abrir essa classe CalculadoraDeSalario e colocar a regra dele aí.

Agora, a pergunta é: e se eu esquecesse? O ponto é às vezes nem esquecer, o ponto é que às vezes eu sou um desenvolvedor que caí de paraquedas no projeto e não conheço tudo. E como essa mudança é indireta, não é clara no meu código – toda vez que um cargo novo aparece eu tenho que mexer na CalculadoraDeSalario eu não vou lembra. Esse é um grande problema de código OO. É a propagação de mudança.

Eu tenho que mexer num único ponto, idealmente. O cliente pediu uma mudança, eu vou num único lugar, e nesse lugar eu mexo, e a mudança propaga pro resto. Eu não tenho que programar usando Ctrl + F. Esse é um ótimo indício de que seu código não está bem orientado a objetos: é quando você tem que, o tempo inteiro, apelar para Ctrl + F, ou pra grep no Linux, alguma forma pra sair buscando código pra descobrir onde tem que mudar.

Idealmente, esses pontos de mudança, eles são sempre explícitos no seu design. No nosso código novo, nós resolvemos esse problema. No próprio enum agora eu tenho lá, eu passo pra ele a regra de cálculo que eu vou usar.

Esse tipo de código, galera, a gente fala que é um problema de encapsulamento. Porque o enum ali, Cargo, deixou vazar pro mundo de fora aonde vai ficar essa regra de cálculo, não estava escondida nele. O Cargo sabe a regra que tem que escolher. Então esse código tem que estar dentro dele.

Está certo? Antes, o nosso código, além de não ser coeso, ele não estava encapsulado. Mais pra frente, tem um capítulo em que eu só vou falar de encapsulamento. Mas já queria alertar pra vocês desde o começo o que é encapsulamento, e olha só como isso é perigoso. Tá bem?

De novo, encapsulamento aqui estava problemático, a regra de cálculo estava saindo da classe Cargo, do enum Cargo. Eu tinha que mexer em outro lugar para colocar a regra que é relativa ao cargo. E agora eu resolvi, certo, sempre que eu criar um enum novo, o compilador vai me encher o saco e vai pedir pra eu passar ali a regra de cálculo. Não tem como eu criar um cargo sem passar a regra de cálculo.

“Ah, mas estou criando um cargo que não tem regra”, ótimo. Você vai criar a regra e vai colocar no enum. Tudo ligadinho no meu sistema, eu não vou ter o problema de esquecer de passar uma regra pra um cargo.

Veja que esse problema, pessoal, no mundo real pode ser pior. Porque eu posso ter 10 classes diferentes que tenham uma regra de negócio relacionada ao cargo. Aqui eu fiz a brincadeira com o salário, mas eu poderia ter 3, 4, 5 outras regras espalhadas em 4, 5 outras classes diferentes. Tá bem?

Voltando aqui para o meu slide, classes coesas, eu as quero o tempo inteiro. Por quê? Porque uma classe coesa é mais fácil de ser lida, eu tenho mais reuso, eu posso pegá-la e levar para um outro sistema, ela provavelmente vai ser mais simples, porque ela vai ter menos código, e eu não vou precisar abri-la o tempo inteiro. Essa é uma coisa com a qual me preocupo bastante, uma classe coesa, ela geralmente vem fechada no meu Eclipse. Porque eu só a abro no caso particular quando eu preciso mudar aquela regra ou quando eu encontrei um problema naquela regra. Mas eu não fico mexendo nela o tempo inteiro.

Essa é a vantagem de uma classe coesa, ela é pequenininha, bem focada, eu sei quando eu tenho que mexer nela, e eu não tenho que mexer nela o tempo inteiro.

No meu slide, eu coloquei a sigla SRP. No começo do curso, eu falei que esse era um curso de Orientação a objetos avançada e SOLID. O que é SOLID? SOLID é o acrônimo, é o conjunto de 5 boas práticas em relação a Orientação a objetos, cada letra fala de uma prática em particular.

O S** nos remete ao **SRP, o Single Responsibility Principle ? em português, Princípio da Responsabilidade Única. A tradução disso, de maneira simples, é coesão. Tenha classes coesas. E aqui, eu discuti com vocês uma maneira de eu conseguir isso, e mais, eu discuti uma maneira de observar que as classes não são coesas.

Comece a prestar atenção nisso no seu dia a dia, quando você está escrevendo uma classe que não para de crescer nunca, esse é um indício de que ela não é coesa. Quando você tem uma classe com 40, 50, 60 métodos, pare e pense “Será que a minha classe, ela tem que ter mesmo 60 comportamentos diferentes? Será que eu não consigo separar isso em classes menores, mais coesas? Então é isso: nesta aula, a lição é coesão. Obrigado.

Acoplamento e a estabilidade - Acoplamento e a estabilidade

Bem-vindo à aula de acoplamento do curso de Orientação a objetos avançada do Alura. Nesta aula, eu vou discutir um pouquinho sobre acoplamento.

Acoplamento é um termo muito comum entre os desenvolvedores, em especial entre aqueles que programam usando linguagens OO. Até porque tem aquela grande frase, a máxima da Orientação a objetos, que é “Tenha classes que são muito coesas e pouco acopladas.”

Neste capítulo, em particular, a parte de coesão vou dar uma deixada de lado, e vou discutir um pouquinho mais sobre acoplamento. Mas vamos lá. Dá uma olhada nesse código. Eu tenho um GeradorDeNotaFiscal e se você olhar o método gera, que é o principal método dessa classe, o que ele faz? Ele pega uma fatura, descobre o valor mensal da fatura, gera uma nota fiscal, certo, faz uma conta lá qualquer com o valor da fatura, isso não vem ao caso. Em seguida, ele manda um e-mail, olha lá email.enviaEmail, e depois ela persiste no dao, dao.persiste. E aí retorna a nota fiscal.

Tanto o enviador de e-mail, quanto o dao, eu estou recebendo ali no construtor da classe GeradorDeNotaFiscal. Excelente. Qual que é o problema desse código? Qual que é o problema do ponto de vista de acoplamento desse código?

Pensa o seguinte: hoje, esse código aqui em particular, ele manda e-mail e ele salva no banco de dados usando um dao. Imagina que amanhã ele também vai ter que mandar pro SAP, ele vai ter que mandar um SMS, ele vai ter que disparar um outro sistema da empresa etc.

Essa classe GeradorDeNotaFiscal, ela vai crescer, ela vai passar a depender de muitas outras classes.

A gente acaba sempre aprendendo que acoplamento é uma coisa muita ruim, “nunca acople o seu sistema”, “faça suas classes não serem acopladas”, mas a pergunta é: por quê? Qual que é o real problema do acoplamento? Por que ele é tão ruim assim? Dê uma olhada no diagrama abaixo:

Diagrama que representa a classe GeradorDeNotaFiscal que depende da classe EnviadorDeEmail, ela também depende da classe NFDAO, e tambem depende da classe SAP.

Hoje, eu tenho um GeradorDeNotaFiscal que depende do EnviadorDeEmail, que depende de um NFDAO, e que depende de um SAP.

O grande problema do acoplamento é que uma mudança em qualquer uma das classes de que eu dependo pode impactar na minha classe principal. Ou seja, se o EnviadorDeEmail parar de funcionar, esse problema pode ser propagado pro GeradorDeNotaFiscal. Se o NFDAO parar de funcionar, o problema vai ser propagado pro gerador. E assim por diante.

Posso até pensar em exemplos de código. Se a interface da classe SAP mudar, essa mudança vai ser propagada para o GeradorDeNotaFiscal. Então, o problema é: a partir do momento em que eu tenho muitas dependências, a minha classe depende de várias.

GeradorDeNotaFiscal depende de muitas outras dependencias

Quer dizer que eu tenho muitas outras classes que podem propagar problemas pra minha classe principal. E é exatamente por isso que o acoplamento é ruim. Minha classe geradora, ela fica muito dependente, muito frágil, muito fácil de ela parar de funcionar.

Esse é o problema do acoplamento. E ele é bem fácil de ser enxergado, certo? E é por isso que a gente tem que tentar diminuí-lo.

Mas agora a grande pergunta é: será que eu consigo zerar o acoplamento? Ou seja, resolver o problema do acoplamento, nenhuma classe vai se acoplar com ninguém. É impossível. Nós sabemos que, na prática, quando estamos fazendo sistemas de médio, grande porte, dependências existirão. O acoplamento vai existir. Eu vou depender de uma classe, minha classe vai depender de outra, e assim por diante.

O grande ponto aqui é começar a diferenciar os tipos de acoplamento. Quando que o acoplamento é realmente problemático, e quando que ele é problemático, mas não tanto assim? Porque se eu conseguir catalogar, eu vou começar a evitar acoplamentos que são realmente perigosos e me acoplar com coisas que são menos perigosas. Essa é a charada, é aonde eu quero chegar nesta aula.

O ponto é: não é sempre que eu fico incomodado com acoplamento. Alguns acoplamentos eu nem lembro que eu estou fazendo. Por exemplo, em Java, quando eu quero lidar com um monte de elementos, eu normalmente uso uma lista, certo? Quando eu estou escrevendo uma string, eu uso a classe String. String e List são classes do mesmo jeito que qualquer outra classe sua. E quando eu faço uso delas, eu estou me acoplando com elas.

Mas aí que está. Quando eu me acoplo com List, eu não sinto tanta dor no coração quanto eu sinto quando eu me acoplo com um DAO por exemplo. Ou com um EnviadorDeEmail, ou com qualquer outra classe que tenha uma regra de negócio associada. A mesma coisa com String. Eu me acoplo a ela, mas me incomoda menos. É um acoplamento que não me dói.

O ponto é: por quê? Qual é a característica de List e qual é a característica de String que faz com que eu tenha menos medo de me acoplar do que com as outras classes? E veja só que essa é a questão chave, porque se eu descobrir o segredo da interface List, eu vou conseguir replicar esse segredo pras minhas classes. E aí, do mesmo jeito que eu não me importo em me acoplar com List, eu não vou me importar em me acoplar com alguma outra coisa do meu sistema.

Quando que é bom, e quando que é ruim? As pessoas geralmente falam assim: “Puxa, acoplar com List não é problema porque List é uma interface que o Java fez. Vem na linguagem Java”. A mesma coisa com a classe String, “String vem com o Java”. Mas não é bem essa resposta que eu procuro.

A resposta, na verdade, é uma característica que List tem, que String tem, que eu preciso replicar nas minhas classes. Dê uma olhada:

Interface List

A interface List, quantas implementações ela tem embaixo dela? ArrayList, LinkedList, qualquer outra coisa List. Aqui no desenho eu coloquei GoogleList - o Google tem um monte de bibliotecas que fazem uso da interface List - etc. e tal.

Eu tenho um monte de implementações de List. Além disso, eu tenho muitas classes que usam List, que dependem de List. O seu código, por exemplo, o meu GeradorDeNotaFiscal, suponha que ele depende de List. É um acoplamento também. Agora, imagina que você está nesse cenário. Você programa lá o Java, você está criando a linguagem Java, você tem acesso ao código-fonte de List, ArrayList, LinkedList etc. e eu peço pra você uma mudança na interface List. Você vai fazer essa mudança?

É claro que não! Porque você sabe que essa mudança é difícil. Mudar a interface List implica em mudar a classe ArrayList, a classe LinkedList e assim por diante. List é uma interface muito importante do meu sistema. Eu não posso mexer nela porque eu sei que essa mudança vai quebrar muitas outras classes. Isso faz com que a interface List seja o que chamamos de estável. Ou seja, ela tende a mudar muito pouco. E se ela tende a mudar muito pouco, quer dizer que a chance de ela propagar um erro, uma mudança, pra classe que a está usando é menor. Consegue ver isso?

Ou seja, se a minha classe depende de List, isso não é um problema porque List não muda. Se ela não muda, eu não vou sofrer impacto com a mudança dela. Esse é o ponto. Eu quero me acoplar com classes, interfaces, módulos, que sejam estáveis. Que tendem a mudar muito pouco.

Essa é a diferença de List pro resto. List muda muito pouco. O nome disso em particular – a gente está sempre acostumado a ver o acoplamento daquele ponto de vista onde eu tenho uma classe e eu dependo de várias outras. Classe GeradorDeNotaFiscal depende de NFDAO, de SAP, de EnviadorDeEmail etc. Isso é o que nós chamamos de acoplamento eferente. Eu, classe, dependo de outras. Mas o outro lado do acoplamento, que é o que eu estou mostrando pra vocês na interface List é também importante, e nós chamamos isso de acoplamento aferente. Eu sou uma classe, e o acoplamento aferente mostra quem depende de mim. Olha só a diferença.

E o que isso mostra pra mim? Quando eu tenho muitas outras classes que dependem de uma classe em específico, isso faz com que essa classe seja estável, com que esse módulo seja estável. Então, o acoplamento do outro lado é importante pra dar essa visão pra gente, de coisas que são estáveis. E por que eu quero isso? Porque eu quero me acoplar com coisas estáveis. Dê uma olhada:

A classe GeradorDeNotaFiscal depende da classe String, depende da classe List

Isso é o que nós temos hoje. GeradorDeNotaFiscal depende de String, depende de List, esse acoplamento me incomoda menos, e aí eu tenho EnviadorDeEmail, NFDAO, SAP e assim por diante. Esses acoplamentos são mais perigosos, pois podem mudar.

O ponto é: como que eu consigo redesenhar isso de maneira a fazer com que o GeradorDeNotaFiscal dependa agora de coisas que são estáveis? Como que eu crio alguma coisa no meu sistema que é estável? Do mesmo jeito que o pessoal lá da Sun (ou da Oracle hoje), fez com List. Eu tenho uma interface List e eu tenho várias implementações embaixo.

Classe GeradorDeNotaFiscal com a interface AcaoAposGerarNota

Eu tenho agora o meu GeradorDeNotaFiscal, eu tenho uma interface AcaoAposGerarNota, e essa interface é implementada por SAP, por EnviadorDeEmail e por NFDAO. Imagina só que eu tivesse mais 10 outras implementações embaixo, que são ações que eu executo depois de gerar a nota.

Essa interface que eu acabei de criar, ela acabou de virar estável. A chance de ela mudar vai ser menor. Porque você, programador, vai ter medo de mexer nela. Mexeu nela, criou um método a mais, mudou uma assinatura de algum método, você vai ter que mudar em todas as implementações abaixo. Isso vai fazer com que ela seja estável, naturalmente.

E se eu fizer o meu GeradorDeNotaFiscal parar de depender do SAP, do EnviadorDeEmail, e do NFDAO, e passar a depender agora de um monte de AcaoAposGerarNota, eu resolvi o problema do acoplamento. Porque agora eu dependo de algo que é bastante estável. É por isso, pessoal, que interfaces têm um papel muito importante em sistemas orientados a objetos. É sempre legal aquela ideia de “Programe voltado pra interface”.

Por quê? Porque além de eu ganhar flexibilidade, certo - porque eu posso ter várias implementações embaixo daquela interface -, aquela interface, ela tende a ser estável. E se ela é estável, me acoplar com ela é um problema menor. Tá certo?

Essa é a grande ideia pra reduzir o problema do acoplamento. Não é deixar de acoplar. É começar a acoplar com coisas estáveis, coisas que tendem a mudar menos. Interface é um bom exemplo disso. Interfaces tendem a mudar menos, porque têm um monte de implementação embaixo, porque interface geralmente só tem um contrato, não tem um código ali dentro, isso faz com que ela seja estável.

Vamos ver isso no código. Eu tenho aqui o GeradorDeNotaFiscal, e ela depende do EnviadorDeEmail e do NotaFiscalDao. E eu sei agora que, pra resolver o problema do acoplamento, eu preciso criar uma interface, essa que, mais pra frente, vai ser estável.

public class GeradorDeNotaFiscal {

    private final EnviadorDeEmail email;
    private final NotaFiscalDao dao;

    public GeradorDeNotaFiscal(EnviadorDeEmail email, NotaFiscalDao dao) {
        this.email = email;
        this.dao = dao;
    }

    public NotaFiscal gera(Fatura fatura) {

        double valor = fatura.getValorMensal();

        NotaFiscal nf = new NotaFiscal(valor, impostoSimplesSobreO(valor));

        email.enviaEmail(nf);
        dao.persiste(nf);

        return nf;
    }

    private double impostoSimplesSobreO(double valor) {
        return valor * 0.06;
    }
}

Vamos lá. O que eu vou fazer aqui é criar uma interface, Ctrl + N, Interface; vou chamar de AcaoAposGerarNota. Essa interface vai ter um método que eu vou chamar de executa(), e ela vai receber – veja só, todos eles recebem uma nota fiscal, certo, todas as dependências recebem uma nota fiscal – então, vou receber também uma nota fiscal aqui.

void executa(NotaFiscal nf);

Aqui no meu gerador, eu vou parar de receber cada um deles em particular (EnviadorDeEmail email, NotaFiscalDao dao) e vou começar a receber uma lista de AcaoAposGerarNota e vou chamar de acoes.

public GeradorDeNotaFiscal(List<AcaoAposGerarNota> acoes)  {

}

List, ele vai importar pra mim, é uma lista convencional do java.util. Vou tirar esses caras fora (private final EnviadorDeEmail email; private final NotaFiscalDao dao;), certo, agora eu não preciso mais, porque agora eu tenho uma lista de ações. E aqui, em vez de fazer email.enviaEmail(nf); dao.persiste(nf);, eu vou fazer um loop. Para cada AcaoAposGerarNota acao na lista de acoes, faco acao.executa(nf);.

public class GeradorDeNotaFiscal  {
    private List<AcaoAposGerarNota>  acoes;
    public GeradorDeNotaFiscal(List<AcaoAposGerarNota>  acoes)  {
        this.acoes = acoes;
    }
    public NotaFiscal gera(Fatura fatura)  { 
        double valor = fatura.getValorMensal();
        NotaFiscal nf = new NotaFiscal(valor , impostoSimplesSobre0(valor));
        for(AcaoAposGerarNota acao  :  acoes)  {`
            acao.executa(nf);
        }
        return nnf;
}

Pra quem já conhece padrão de projeto, e fez até o nosso curso aqui online sobre o assunto, percebeu que o que eu fiz aqui foi usar o observer. Esse padrão aqui chama observer. E veja só como ele resolve bem o problema do acoplamento. Eu passo a depender de uma lista de ações, AcoesAposGerarNota, e assim que a ação acontece, eu notifico todas as ações pra que cada uma delas faça seu trabalho.

O que eu preciso agora é implementar essas ações. No dao, tenho que implementar AcaoAposGerarNota.

public class NotaFiscalDao implements AcaoAposGerarNota  {

}

Tá legal? Erro de compilação, claro, porque o método executa não está escrito, eu vou substituir o nome:

public class NotaFiscalDao implements AcaoAposGerarNota  {
    public void executa(NotaFiscal nf)  {
        System.out.println("salva nf no banco");
    }
}

A implementação aqui de exemplo é o sysout, mas na prática, é o código que acessa o banco de dados etc.

A mesma coisa no EnviadorDeEmail. Eu vou implementar AcaoAposGerarNota. Vou mudar o método aqui de enviaEmail para executa, que é o método da interface, e tudo funcionando.


public class EnviadorDeEmail implements AcaoAposGerarNota  {

    public void executa(NotaFiscal nf)  {

Agora, basta eu, na hora de instanciar o GeradorDeNotaFiscal, passar todas as ações que eu quero, certo? new NotaFiscalDao, new EnviadorDeEmail, e assim por diante.

Eu resolvi o problema de acoplamento agora do GeradorDeNotaFiscal usando interfaces e, mais do que isso, dependendo de um módulo estável que eu criei, que é o AcaoAposGerarNota. Olha só que simples.

Bem, então o que nós vimos neste capítulo? Nós discutimos um pouquinho do que é acoplamento, e por que ele é ruim. O acoplamento é ruim porque quando eu tenho uma classe que depende de outra classe, mudanças nas classes de que eu dependo podem afetar a classe principal. Isso é problemático. Eu preciso diminuir isso.

Como eu faço isso? Eu tento me acoplar com classes, interfaces, módulos, que sejam estáveis. Um módulo estável é aquele que tenta mudar muito pouco. Ele tem alguma coisa ao redor dele que faz ele mudar muito pouco. E eu mostrei que, no caso da interface, o número de implementações embaixo, o número de pessoas usando aquela interface, são uma força contra mudança nela. Então, acople-se com coisas que são estáveis. E evite ao máximo acoplamento com coisas instáveis no seu sistema. E por hoje é isso, obrigado!

Classes abertas, Open Closed e Dependency Inversion Principles - Classes abertas, Open Closed e Dependency Inversion Principles

Olá pessoal! Nas aulas passadas, discutimos sobre acoplamento e sobre coesão. Sobre acoplamento, em particular, eu falei bastante pra vocês sobre classes estáveis. Você lembra disso? A classe estável é aquela que tende a mudar muito pouco. Qual que é a vantagem disso? A vantagem é que se ela muda muito pouco é melhor que eu me acople a ela, afinal, ela não vai propagar a mudança pra mim.

Sempre que eu quiser pensar em acoplamento, ou precisar me acoplar com alguma outra classe ou módulo, a ideia é que eu me acople com módulos que são estáveis. Ótimo.

Com essa ideia na cabeça, dá uma olhada nesse código aí que eu vou dar pra vocês:


public class CalculadoraDePrecos {

    public double calcula(Compra produto) {
        TabelaDePrecoPadrao tabela = new TabelaDePrecoPadrao();
        Frete correios = new Frete();

        double desconto = tabela.descontoPara(produto.getValor());
        double frete = correios.para(produto.getCidade());

        return produto.getValor() * (1-desconto) + frete;
    }
}

public class TabelaDePrecoPadrao {
    public double descontoPara(double valor) {
        if(valor>5000) return 0.03;
        if(valor>1000) return 0.05;
        return 0;
    }
}

public class Frete {
    public double para(String cidade) {
        if("SAO PAULO".equals(cidade.toUpperCase())) {
            return 15;
        }
        return 30;
    }
}

Eu tenho uma calculadora de preço. A ideia dela é basicamente pegar um produto da minha loja e tentar descobrir o preço desse produto. Ele vai primeiro pegar o preço do produto bruto, aí vai usar essa tabela de preços padrão (TabelaDePrecoPadrao) pra calcular o preço, porque pode ter um eventual desconto, em seguida, ele vai descobrir também o valor do frete, porque eu tenho que mandar esse produto pelos correios, e no final, ele faz a conta ali. Ele pega o produto, multiplica pelo valor do desconto, mais o frete, uma regra convencional.

Percebe também que aqui eu tenho várias classes, porque eu estou pensando bastante em coesão, então, idealmente, eu tenho classes pequenas, bastante coesas, com pouca responsabilidade.

Eu tenho essa TabelaDePrecoPadrao, que tem uma regra de negócio, está numa classe. Eu tenho a classe Frete que toma conta ali de calcular o frete também, numa outra classe. E, na calculadora, essa classe depende das outras duas.

Tá legal, está tudo funcionando, e está perfeito. Só que agora pensa no seguinte: imagina que o meu software vá crescer. Então, eu não tenho só a tabela de preços padrão. Eu tenho a tabela de preços padrão e a tabela de preços diferenciados. Pra entrega, eu não uso só os correios. Eu uso os correios, ou estou usando uma outra empresa particular também de entrega de produtos. Imagina que a regra cresceu. Está certo? Eu tenho, de acordo com meu produto, de acordo com o cliente eu calculo o preço de maneira diferente. Como que eu vou implementar isso?

Eu tenho duas maneiras. Vamos lá. A primeira delas seria colocar um if na CalculadoraDePrecos. Dá uma olhada: if(REGRA 1), uma regra qualquer, eu não especifiquei uma regra, mas imagina que eu tenha uma condição qualquer. Se a regra não acontecer, eu vou fazer TabelaDePrecoPadrao e vou usá-la. Caso contrário, se for a REGRA 2, ele vai usar a TabelaDePrecoDiferenciada.


public class CalculadoraDePrecos {

    public double calcula(Compra produto) {

        Frete correios = new Frete();

        double desconto;
        if (REGRA 1){
            TabelaDePrecoPadrao tabela = new TabelaDePrecoPadrao();
            desconto = tabela.descontoPara(produto.getValor());

        }
        if (REGRA 2){
            TabelaDePrecoDiferenciada tabela = new TabelaDePrecoDiferenciada();
            desconto = tabela.descontoPara(produto.getValor());
        }
        double frete = correios.para(produto.getCidade());
        return produto.getValor() * (1 - desconto) + frete;
    }
}

A mesma coisa lá pro frete. Aqui eu não coloquei nesse código, mas imagina a mesma coisa. Se cair na regra 1, usa os correios, se cair na regra 2, usa empresa XPTO também de entrega.

Não parece uma boa ideia, certo, porque eu vou começar a encher esse código de if, esse código vai ficar complicado; essa classe começa a perder coesão porque ela começa a saber de muita coisa; o acoplamento vai crescer, porque ela vai depender da TabelaDePrecoPadrao, da diferenciada, dos correios, da empresa XPTO e assim por diante. Está complicado.

Segunda alternativa: seria fazer separado. Eu pego a minha classe Frete e eu coloco esse if na classe de frete. Se eu estou na REGRA 1, faz desse jeito, se eu estou na REGRA 2, faz daquele jeito.


public class Frete {

    public double para(String cidade) {
         if(REGRA 1)  {
              if("SAO PAULO".equals(cidade.toUpperCase())) {
                            return 15;
              }
            return 30;
            }

        if(REGRA 2) { ... }
        if(REGRA 3) { ...}
        if(REGRA 4) { ...}
    }
}

A mesma coisa para a TabelaDePrecoPadrao. Se eu estou na REGRA 1, dá esse desconto, se eu estou na REGRA 2, dá aquele outro desconto, e assim por diante.


public class TabelaDePrecoPadrao {

    public double descontoPara(double valor) {
            if(REGRA 1) {
                if(valor>5000) return 0.03;
                if(valor>1000) return 0.05;
                return 0;
        }

        if(REGRA 2) { ... }
        if(REGRA 3) { ...}
        if(REGRA 4) { ...}
    }
}

O problema é que também a complexidade vai crescer, certo? Imagina que eu tenho 10 regras, eu vou ter 10 ifs aí, o código vai ficar difícil de manter. Veja só, nos dois códigos que eu dei pra vocês, no primeiro deles o acoplamento ia crescer, porque a classe CalculadoraDePrecos ia começar a depender de muitas outras classes. No meu segundo caso, aqui, a coesão dessas classes Frete e TabelaDePrecoPadrao também ia complicar.

Então, acoplamento, coesão... Eu estou falando pra vocês o tempo inteiro que a grande graça de programar orientado a objetos é balancear entre essas duas coisas. Eu nunca vou conseguir ter máxima coesão e zero acoplamento. A ideia é encontrar esse equilíbrio. Tá legal? Vamos lá.

O primeiro conceito que eu quero passar pra vocês é a ideia de que as classes têm que ser abertas. Mas como assim “aberta”, o que que é uma classe aberta? Eu coloquei aí até a sigla OCP (Open Closed Principle), que é o princípio que fala disso. Mas que raio que é esse negócio de princípio do aberto e fechado, o que são classes abertas?

A ideia é que as suas classes sejam abertas para extensão. Ou seja, eu tenho que conseguir estendê-la, ou seja, mudar o comportamento dela, de maneira fácil. Mas ela tem que estar fechada para alteração. Ou seja, eu não tenho que ficar o tempo inteiro indo nela pra mexer um if a mais, para fazer uma modificação ou coisa do tipo. Então, de novo, fechada para modificação, ou seja, eu não quero ter que o tempo inteiro entrar nela e sair escrevendo código, mas ela tem que estar aberta para extensão, ou seja, eu tenho que conseguir mudar a execução dela ao longo do tempo.

Meio maluco isso, né? Como que eu faço isso? Eu vou mostrar pra vocês em código, e aí vai ficar muito mais claro. Vamos lá. Esse é o código que eu tenho agora. E eu sei que eu quero evitar qualquer tipo de if, sei lá, if(regra1) calcula desse jeito, caso contrário, e assim por diante. Preciso evitar esse if tanto aqui quanto dentro das implementações, de tabela de preço, de frete etc. e tal.

Afinal, está tudo bonitinho, como eu mostrei pra vocês. Esse código é simples, super coeso, esse código aqui do Frete também é simples, super coeso, a CalculadoraDePrecos também.

Mas a gente precisa mudar o comportamento, e é isso que vai acontecer no mundo real. Então, a primeira coisa que eu vou fazer é pensar numa abstração. Já que eu tenho diferentes tabelas de preço, eu preciso pensar numa abstração comum entre todas elas. E, por enquanto, vai ser o próprio método que eu tenho aqui, esse double descontoPara(double valor).

A primeira coisa que eu vou fazer é criar uma interface que vai representar essa abstração pra mim. Eu vou chamar de TabelaDePreco. O único método vai ser double descontoPara.


public interface TabelaDePreco  {
    double descontoPara(double valor);
}

Essa classe aqui, TabelaDePrecoPadrao implementa a interface TabelaDePreco:


public class TabelaDePrecoPadrao implements TabelaDePreco  {

Vou fazer a mesma coisa pro Frete. Vou chamar aqui (a nova interface) de ServicoDeEntrega. O método que ele vai ter lá é esse double para que recebe uma cidade:


public interface ServicoDeEntrega  {
    double para(String cidade);
}

E aqui o Frete vai implementar ServicoDeEntrega:


public class Frete implements ServicoDeEntrega  {

Ótimo, está tudo perfeito. E eu sei que essas interfaces, elas tenderão a ser estáveis. Agora, o que eu vou fazer é o seguinte:


TabelaDePrecoPadrao tabela = new TabelaDePrecoPadrao();
Frete correios = new Frete();

Esse new é o que me incomoda aqui. Os dois. O ponto é: eu preciso fazer com que seja possível eu trocar a implementação da TabelaDePreco. Como que eu vou fazer isso? Eu vou receber pelo construtor. Então, CalculadoraDePrecos vai ser uma TabelaDePreco no construtor, a interface vou chamar de tabela, e vai receber um ServicoDeEntrega, vou chamar aqui de entrega.


public class CalculadoraDePrecos  {

    public CalculadoraDePrecos(TabelaDePreco tabela, ServicoDeEntrega entrega) {
    }

Vou guardar esses dois parâmetros que eu recebi no construtor como atributos da classe, afinal eu vou precisar usar nesses métodos aqui. Vou jogar os dois news fora, e aqui não é mais correios, eu mudei o nome para entrega:


double frete = entrega.para(produto.getCidade());

Dá uma olhada nesse código, como está muito melhor. Eu vou até escrever aqui um método de teste para você entender. Eu vou escrever uma classe que se chama Teste, que vai ter uma main qualquer, e aqui vou fazer o seguinte:


public class Teste  {
    public static void main(String[] args)  {
        new CalculadoraDePrecos(tabela, entrega)
    }
}

Olha só, dei new nessa classe. Eu preciso passar pra ela uma tabela e uma entrega. Vou criar duas variáveis locais aqui pra ficar mais claro ainda. E agora, qual tabela de preço que eu passo? A que eu quiser! Então, por exemplo, TabelaDePrecoPadrao. Qual serviço de entrega eu passo? new Frete():


public class Teste  {
    public static void main(String[] args)  {
        TabelaDePreco tabela = new TabelaDePrecoPadrao();
        ServicoDeEntrega entrega = new Frete();

E olha só, aqui eu tenho a minha calculadora que funciona de um jeito, com a TabelaDePrecoPadrao e com o Frete.


    CalculadoraDePrecos calculadora = new CalculadoraDePrecos(tabela, entrega);

Quando eu tiver uma outra implementação, então muda para TabelaDePrecoDiferenciada, dá uma olhada. Esse código, vamos fazê-lo compilar rapidinho.

Vou criar a classe, já implementando a interface, certo, o Eclipse é inteligente. Mas veja só! A minha CalculadoraDePrecos continua funcionando! Só que o comportamento dela vai ser diferente, porque, quando ela for usar a TabelaDePreco, ele vai usar qual tabela de preço? A diferenciada.

Veja só que eu consegui mudar o comportamento da CalculadoraDePrecos sem mexer no código dela. Simplesmente porque eu mudei a ferramenta de trabalho, a dependência que ela recebe. Isso que é uma classe aberta para extensão. Eu consigo mudar como que ela vai funcionar, passando, por exemplo, uma dependência pelo construtor.

Por que que isso deu certo? Porque eu pensei bem esse meu código! Eu criei uma abstração TabelaDePreco. É uma interface. Se é uma interface, logo, eu vou ter n implementações. E qualquer implementação vai entrar nessa porta da TabelaDePreco, o polimorfismo vai fazer a mágica pra mim.

Veja só como está muito melhor. Essa classe evolui agora facilmente. Eu consigo mudar a TabelaDePreco, consigo mudar ServicoDeEntrega, e esse código CalculadoraDePrecos está fechado. Porque eu não vou precisar mexer nele. Então, está aberto para extensão, mas está fechado para modificação. Isso é o tal do OCP. Olha só que código bacana, né?!

Legal! Viu o que a gente fez? Eu criei uma abstração, que eu chamei de TabelaDePreco, outra de ServicoDeEntrega, fiz a minha classe CalculadoraDePrecos depender dessas interfaces, e veja bem, essas interfaces são estáveis, elas tendem a mudar muito pouco. Está tudo beleza com o acoplamento, e veja que agora minha classe é aberta! Porque eu consigo mudar o comportamento dela. Então, dependendo da TabelaDePreco que eu passar, a minha calculadora vai funcionar de uma maneira diferente. Dependendo da empresa de frete que eu passar, a minha calculadora vai funcionar também de uma maneira diferente.

Ela está aberta para extensão. E veja só como eu estendi: mudando a implementação que eu passo pras dependências no construtor. E ela está fechada para modificação. Eu não preciso ir nela para mudar o comportamento da TabelaDePreco, eu não preciso ir nela para mudar o comportamento do Frete. Se aparecer um frete novo, eu crio uma nova classe, e a classe CalculadoraDePrecos vai continuar funcionando pra isso.

Isso é o tal do OCP, o Princípio do Aberto e Fechado. E veja só como eu usei, como eu lidei com ele, como que eu joguei com esse problema aí do acoplamento/ coesão. Criei uma interface, que é estável, recebi pelo construtor, e isso fez agora com que eu possa mudar o comportamento da minha classe principal simplesmente mudando a implementação que eu estou passando pra essa classe CalculadoraDePrecos.

Isso é programar orientado a objetos. É pensar em abstração. Quando eu tenho uma boa abstração, eu consigo simplesmente evoluir o meu sistema criando novas implementações das abstrações em que eu já pensei antes. Agora meu sistema está lindo e maravilhoso. Ele evolui facilmente, basta eu criar novas implementações, as classes são todas coesas, são simples, são fáceis de serem testadas de maneira automatizada.

Mas e esse tal do DIP, o Princípio da Inversão de Dependências? Isso você já sabe o que é, eu só não tinha dado o nome. Sabe essa ideia maluca de você sempre depender de classes que são estáveis? Dá pra generalizar esse conceito.

A ideia é: sempre que você for depender, depende de alguém mais estável. Então, A* depende de *B, a ideia é que B* seja mais estável que *A. Mas B* depende de *C. Então, a ideia e que C* seja mais estável que *B. A ideia é que você sempre passe a depender de modos mais estáveis que você.

E mais do que isso, esse princípio vai mais longe. Ele fala o seguinte: “Olha, se você está numa classe, tenta depender de abstração. Você não pode depender de implementação. Dependa sempre de abstrações.”

Se você está numa abstração, a ideia é de que a abstração não conheça a implementação. Consegue ver o caminho? Dependa sempre de abstração, porque abstração é estável. Nunca dependa de implementação.

E a abstração, por sua vez, ela só pode conhecer outras abstrações. A ideia é que ela não conheça detalhes de implementação. Isso é o que nós chamamos de Dependency Inversion Principle, o Princípio de Inversão de Dependência. Não confunda isso com “injeção” de dependência. Injeção de dependência é aquela ideia de você ter os parâmetros no construtor, e alguém magicamente injetar essas dependências pra você. O nome é parecido. Aqui é o princípio da inversão de dependência. A ideia é que você está invertendo a maneira de você depender das coisas. Passa a depender agora de abstrações.

Isso é OCP e isso é DIP. Eu deixei pra falar dele só agora, porque agora você entende bem o que é coesão, entende bem o que é acoplamento, e entende estabilidade. Agora você tem ferramenta suficiente pra jogar e entender essa ideia aí do OCP.

Sempre que eu programo, eu penso muito em abstração. O tempo inteiro. Porque a abstração vai me dar um monte de vantagem. Ela vai deixar que minha classe seja aberta o tempo inteiro, então eu posso mudar, criar uma nova implementação, e minha classe que depende da abstração vai funcionar com ela. A abstração é estável, então ela não vai propagar mudança problemática pra classe principal, e assim por diante.

Programar orientado a objetos é pensar em abstração. Quando eu estou dando aula de Orientação a objetos básica, e aí o cara está vendo pela primeira vez todas aquelas ideias malucas de polimorfismo, herança, encapsulamento etc. e tal, uma brincadeira que eu sempre faço com eles é: no meio da aula eu falo “Gato, cachorro e pássaro”. Eu espero que eles me respondam “animal”. Eu viro e falo “ISS, INXPTO e sei-lá-o-que-das-quantas”, outros nomes de imposto, e eu espero que a pessoa me fale “imposto”. Eu faço o tempo inteiro o meu aluno pensar em abstração. Isso é programar orientado a objetos. É pensar primeiro na abstração, e depois, na implementação.

Essa é uma mudança de pensamento com quem programa procedural. Porque no mundo procedural, você está muito preocupado com a implementação. E é natural. No mundo OO, você tem que inverter: a sua preocupação maior tem que ser com a abstração, com o projeto de classes.

Pense no seu projeto. A implementação é importante, o código ali que vai fazer a coisa funcionar, o if, o for, é importante. Mas no sistema OO, pensar no projeto de classes é fundamental. É isso que vai garantir a facilidade de manutenção.

Eu tinha lá a minha CalculadoraDePrecos e agora eu dependo de uma interface Frete, dependo de uma interface TabelaDePrecos. E aí basta eu passar implementações concretas de cada um deles, que a minha CalculadoraDePrecos vai mudar.

Resumindo, falei nesse capítulo pra vocês de classes abertas, e o tal do OCP (o Princípio do aberto e fechado) – a ideia é que suas classes sejam abertas para evolução, mas fechadas pra mudança. E eu falei pra vocês do DIP (Dependency Inversion Principle), cuja ideia é você inverter a dependência, e sempre depender de abstrações. Porque abstrações são legais, são estáveis etc. e tal.

Esse foi o conteúdo dessa aula, e isso mostra pra gente as outras duas letrinhas aí do SOLID, o O** do OCP, e o **D do DIP.

Sobre o curso SOLID com Java: Orientação a Objetos com Java

O curso SOLID com Java: Orientação a Objetos com Java possui 85 minutos de vídeos, em um total de 32 atividades. Gostou? Conheça nossos outros cursos de Java em Programação, ou leia nossos artigos de Programação.

Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:

Aprenda Java acessando integralmente esse e outros cursos, comece hoje!

  • 1241 cursos

    Cursos de programação, UX, agilidade, data science, transformação digital, mobile, front-end, marketing e infra.

  • Certificado de participação

    Certificado de que assistiu o curso e finalizou as atividades

  • App para Android e iPhone/iPad

    Estude até mesmo offline através das nossas apps Android e iOS em smartphones e tablets

  • Projeto avaliado pelos instrutores

    Projeto práticos para entrega e avaliação dos professores da Alura com certificado de aprovação diferenciado

  • Acesso à Alura Start

    Cursos de introdução a tecnologia através de games, apps e ciência

  • Acesso à Alura Língua

    Reforço online de inglês e espanhol para aprimorar seu conhecimento

Premium

  • 1241 cursos

    Cursos de programação, UX, agilidade, data science, transformação digital, mobile, front-end, marketing e infra.

  • Certificado de participação

    Certificado de que assistiu o curso e finalizou as atividades

  • App para Android e iPhone/iPad

    Estude até mesmo offline através das nossas apps Android e iOS em smartphones e tablets

  • Projeto avaliado pelos instrutores

    Projeto práticos para entrega e avaliação dos professores da Alura com certificado de aprovação diferenciado

  • Acesso à Alura Start

    Cursos de introdução a tecnologia através de games, apps e ciência

  • Acesso à Alura Língua

    Reforço online de inglês e espanhol para aprimorar seu conhecimento

12X
R$75
à vista R$900
Matricule-se

Premium Plus

  • 1241 cursos

    Cursos de programação, UX, agilidade, data science, transformação digital, mobile, front-end, marketing e infra.

  • Certificado de participação

    Certificado de que assistiu o curso e finalizou as atividades

  • App para Android e iPhone/iPad

    Estude até mesmo offline através das nossas apps Android e iOS em smartphones e tablets

  • Projeto avaliado pelos instrutores

    Projeto práticos para entrega e avaliação dos professores da Alura com certificado de aprovação diferenciado

  • Acesso à Alura Start

    Cursos de introdução a tecnologia através de games, apps e ciência

  • Acesso à Alura Língua

    Reforço online de inglês e espanhol para aprimorar seu conhecimento

12X
R$100
à vista R$1.200
Matricule-se

Max

  • 1241 cursos

    Cursos de programação, UX, agilidade, data science, transformação digital, mobile, front-end, marketing e infra.

  • Certificado de participação

    Certificado de que assistiu o curso e finalizou as atividades

  • App para Android e iPhone/iPad

    Estude até mesmo offline através das nossas apps Android e iOS em smartphones e tablets

  • Projeto avaliado pelos instrutores

    Projeto práticos para entrega e avaliação dos professores da Alura com certificado de aprovação diferenciado

  • Acesso à Alura Start

    Cursos de introdução a tecnologia através de games, apps e ciência

  • Acesso à Alura Língua

    Reforço online de inglês e espanhol para aprimorar seu conhecimento

12X
R$120
à vista R$1.440
Matricule-se
Procurando planos para empresas?

Acesso por 1 ano

Estude 24h/dia onde e quando quiser

Novos cursos todas as semanas