segunda-feira, 17 de março de 2014

Java, C#: Substituindo o Switch com Polimorfismo

Fala galera, tudo tranquilo? Passado o Carnaval, espero que tenham se divertido, eu estou há uma semana sem escrever e hoje, vou escrever algo não muito novo. O exemplo acontece em Java, mas é simples de replicar em C#, acredite.

Recebi um desafio na faculdade, de escrever uma estrutura de tomada de decisão, aquela famosa "coisa bizarra" chamada Switch.

Imagine o seguinte, nós temos 4 regiões, sendo elas: Sul, Norte, Leste e Oeste. E para cada valor setado, desejo retornar uma região diferente, por exemplo:

Região Valor
Sul 2
Norte 3
Leste 5
Oeste 7

Com essa tabela ficou mais claro, se eu passar como parâmetro o valor 2, quero receber a região Sul. Bom, escrevendo o teste, poderemos refatorar depois, certo?

Crie um projeto no Eclipse/Visual Studio, e dê o nome de SubstituindoSwitch, feito isso, crie um source folder de test e crie um pequeno teste de unidade, em seu interior:

public class SustituindoSwitchTest {

    @Test
    public void DeveBuscarRegiaoSul() {
        Assert.assertEquals("Sul", new SubstituindoSwitch().buscaRegiao(2));
    }

    @Test
    public void DeveBuscarRegiaoNorte() {
        Assert.assertEquals("Norte", new SubstituindoSwitch().buscaRegiao(3));
    }

    @Test
    public void DeveBuscarRegiaoLeste() {
        Assert.assertEquals("Leste", new SubstituindoSwitch().buscaRegiao(5));
    }

    @Test
    public void DeveBuscarRegiaoOeste() {
        Assert.assertEquals("Oeste", new SubstituindoSwitch().buscaRegiao(7));
    }

}

Agora vamos resolver da forma mais simples. E o Switch cai como uma luva certo? Olha ele aí:

public class SubstituindoSwitch {

    public String buscaRegiao(int valor) {

        switch (valor) {
        case 2:
            return "Sul";
        case 3:
            return "Norte";
        case 5:
            return "Leste";
        case 7:
            return "Oeste";
        default:
            return "Região não encontrada.";
        }
    }
}

Bom, resolvemos o problema certo? Sim! Mas se temos toda aquela história de orientação a objetos e polimorfismo, por que usamos essa estrutura forrada de responsabilidades e totalmente procedural?

Podemos começar a pensar apenas em dividir as responsabilidades, ali no switch ficou claro que nossa classe SubstituindoSwitch tem uma grande responsabilidade de avaliar e claro devolver o retorno esperado e isso não é nada vantajoso. Vamos tentar encontrar semelhanças na responsabilidade. Para todas as situações um determinado valor é avaliado e caso seja ele o correto, recebemos uma String. E aí identificamos uma responsabilidade, vamos construir uma interface para esses responsabilidade:

public interface Regiao {
    public boolean avalia(int valor);

    public String procedencia();
}

Pronto, já definimos a responsabilidade, agora precisamos delegar os respectivos comportamentos, sabemos que o quando o valor for 7, desejamos receber a string Oeste, ok?

public class Oeste implements Regiao {
    @Override
    public boolean avalia(int valor) {
        return valor == 7;
    }

    @Override
    public String procedencia() {
        return "Oeste";
    }
}

E podemos fazer isso para todas as outras três regiões esperadas(Sul, Norte e Leste), cada uma dessas regiões com a mesma responsabilidade mas diferentes comportamentos. Certo? Implemente as outras classes para podermos dar continuidade. Caso a preguiça domine, vá até meu Github.
Agora voltaremos a nossa classe SubstituindoSwitch, e faremos algumas pequenas modificações, a primeira de todas é apagar o devorador de responsabilidades switch:

public class SubstituindoSwitch {
    private List<Regiao> regioes;

    public SubstituindoSwitch() {
        regioes = new ArrayList<>();

        regioes.add(new Sul());
        regioes.add(new Norte());
        regioes.add(new Leste());
        regioes.add(new Oeste());

    }

    public String buscaRegiao(int valor) {

        for (Regiao regiao : regioes) {
            if (regiao.avalia(valor))
                return regiao.procedencia();
        }

        return "Região não encontrada.";
    }
}

Vou tentar ao máximo deixar claro o que está acontecendo no código acima, a primeira coisa que fizemos foi definir uma lista de regiões, ou seja, uma lista que armazenará todas as opções de regiões possíveis. Nosso método buscaRegiao continuou recebendo o parâmetro valor, mas ele não mais detém a responsabilidade de conter os valores das regiões, ele apenas delega a responsabilidade de acordo com uma avaliação também executada por cada responsável pelo valor, poxa, perdi até o fôlego. Detalhando:

if (regiao.avalia(valor)) return regiao.procedencia();

Esse if passa o valor para a região, a região detém o comportamento esperado e ela retorna o resultado desejado. Bom, só rodar os testes, e se maravilhar com o resultado. Bom, o próximo desafio é substituir o if, aí já envolve um pouco mais de estudo. :P

Qualquer dúvida, comente abaixo ou email-me: abner.terribili@lambda3.com.br.

Se surgir dúvidas, tem o exemplo lá no meu Github:
Para os Java: https://github.com/aterribili/SubstituindoSwitch
Para os C#: https://github.com/aterribili/SubstituindoSwitch-CSharp


Abraços!

Nenhum comentário:

Postar um comentário