Open Closed Principle - PauloGustavo72/solid GitHub Wiki

Esse princípio nos diz que nossa classe deve ser aberta para extensão. Ou seja, tem que ser de fácil uso por outras classes. Mas o princípios também diz que ela deve ser fechada para mudança. Isso quer dizer que devemos faze-lá de alguma forma com que não voltemos nela toda hora para alterar um if, else ou qualquer coisa que seje.

Veja o código abaixo:

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

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.

Percebemos aqui que, sem pensar em uma estrutura legal, essa classe irá fica cheia de ifs, segue um exemplo:

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

O problema disso é a complexidade. Imagina que eu tenho 10 regras diferentes, vão ser 10 ifs e possívelmente vou depender de mais classes.

Solução:

  • Abstração: Primeiro vamos criar abstrações para as classes TabelaDePrecoPadrao e Frete, conforme imagens abaixo:

    public interface TabelaDePreco  {
      double descontoPara(double valor);
    }
    
    public class TabelaDePrecoPadrao implements TabelaDePreco
    
    public interface ServicoDeEntrega  {
      double para(String cidade);
    }
    
    public class Frete implements ServicoDeEntrega  {
    
  • Agora fazemos com que a classe CalculadoraDePrecos receba as interfaces acima como argumento no construtor:

    public class CalculadoraDePrecos  {
    
     public CalculadoraDePrecos(TabelaDePreco tabela, ServicoDeEntrega entrega) {
     }
    }
    
  • Assim conseguimos dizer por exemplo que o método entrega vai ser calculado acima da implementação passada pelo construtor, segue um exemplo:

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