C# - Instruções Condicionais não é uma boa prática OOP
 Veja as opções para criar objetos na linguagem C# e escolha a melhor opção.

As instruções condicionais, como if-else e switch, são essenciais para qualquer linguagem de programação. Nós usamos essas instruções praticamente todo o dia. No entanto, você sabia que essas instruções não são consideradas uma boa prática no contexto da programação orientada a objetos ?

A melhor forma de mostrar o porquê as instruções condicionais são um code smell ou cheiro de código é começar com um exemplo simples.

Nota:  Para acompanhar o artigo você tem que ter noções sobre os princípios SOLID.

Apresentando o problema

Vamos supor que temos uma classe Animal que contém o método GetSons() conforme descrito abaixo:

Aqui temos um bloco de código usando o condicional if/else que realiza várias ações dependendo do tipo de objeto.

Este código viola claramente o Princípio da Responsabilidade Única(SRP) que defende que uma classe deve ter uma única responsabilidade, ou seja um único motivo para ser alterada.

Isso ocorre neste código estamos definindo mais de um comportamento ou responsabilidade como Cachorro, Gato e Papagaio, e, assim temos 3 motivos para alterar esta classe.

Além disso, este código também viola o princípio Aberto/Fechado.(OCP) pois se precisarmos incluir um novo tipo de animal o código acima terá que ser alterado incluindo mais uma condição 'else if', e isso vai forçar a modificar a classe Animal. Toda vez que precisarmos adicionar/estender alguma funcionalidade, devemos alterar seu código-fonte.

Com isso ferimos dois importantes princípios SOLID.

Como podemos consertar isso ?

Existem várias receitas de refatoração para remover condicionais do seu código como por exemplo:

  1. Usar o padrão State/Strategy;
  2. Usar polimorfismo;

Usando Polimorfismo

Polimorfismo significa muitas formas, na orientação a objetos você pode enviar uma mesma mensagem para diferentes objetos e fazê-los responder da maneira correta.

Usando polimorfismo podemos :

  1. Invocar métodos da classe derivada através da classe base em tempo de execução;
  2. Permitir que classes forneçam diferentes implementações de métodos que são chamados com o mesmo nome;

Existem dois tipos básicos de polimorfismo:

  1. Polimorfismo em tempo de compilação (Overloading/Sobrecarga);
  2. Polimorfismo em tempo de execução (Overriding/Sobrescrita);

Na maioria dos casos, quando substituímos uma instrução condicional por polimorfismo, lidamos com um subtipo polimorfismo. Esse tipo de polimorfismo na POO significa a capacidade de alterar o comportamento do método fornecendo um método com o mesmo nome em uma classe filha.

Vamos então aplicar essa abordagem que usa o polimorfismo para refatorar o código e assim nos livrar das instruções condicionais. Veja como o código deve ficar :

Veja que agora a classe Animal foi simplificada, e toda a lógica referente a cada tipo de Animal foi movida para a sua respectiva classe.

A implementação acima está valorizando o princípio de responsabilidade única encapsulando o comportamento e a responsabilidade nas classes correspondentes. A princípio, o comportamento de todos os animais é encapsulado em uma classe, fazendo com que a classe viole o princípio da responsabilidade única.

Sendo transformadas em subclasses separadas, agora temos as responsabilidades empurradas para elas, então qualquer mudança no comportamento de “Gato” é responsabilidade da classe Gato e não da classe “Animal”, e o mesmo vale para os demais tipos.

A nossa implementação também agora esta aderente ao princípio Aberto/Fechado, vamos ver como. Observe o código a seguir:

public abstract class Animal
{
    public abstract string EmitirSom();

    private static String GetSomPolimorfico(Animal animal)
    {
        return animal.EmitirSom();
    }
}

Note que o método GetSomPolimorfico() depende da abstração (Animal), então qualquer classe que implemente a abstração funcionará neste método. Além disso, se for necessário implementar um novo comportamento como “Peixe”, não precisaremos fazer nenhuma alteração no método  GetSomPolimorfico() nem na abstração Animal.

Temos apenas que criar uma nova classe “Peixe”, e isso prova que “Animal” está aberto para mais extensões e não esta forçando nenhuma modificação.

Esta técnica segue o princípio Tell-Don't-Ask (diga não peça) que prega que em vez de perguntar a um objeto sobre seu estado e então realizar ações baseadas nisso, é muito mais fácil simplesmente dizer ao objeto o que ele precisa fazer e deixá-lo decidir por si mesmo como fazer isso.

Com isso removemos o código duplicado e nos livramos das instruções condicionais.

Se você precisar adicionar uma nova variante de execução, tudo o que você precisa fazer é adicionar uma nova subclasse sem tocar no código existente (Princípio Aberto/Fechado).

E estamos conversados.

"Multidões que dormem no pó da terra acordarão: uns para a vida eterna, outros para a vergonha, para o desprezo eterno.
Aqueles que são sábios reluzirão como o brilho do céu, e aqueles que conduzem muitos à justiça serão como as estrelas, para todo o sempre."
Daniel 12:2,3

Referências:


José Carlos Macoratti