C# - Substituindo bloco switch/case, if/else por polimorfismo
Hoje vamos mostrar porque e quando substituir o bloco de código condicional por polimorfismo na linguagem C#, |
Na programação orientada a objetos você vai encontrar com abundância a utilização do if/else e o switch/case que pode realizar diversas ações dependendo do tipo de objeto ou propriedade.
Mas é errado usar if/else ou switch/case no código ?
Claro que não.
O problema ocorre quando temos que fazer a verificação de um tipo de objeto a fim de tomar alguma decisão lógica usando blocos if/else ou switch/case.
Nestes casos quando você está fazendo verificações por tipo e executando algum tipo de operação, é uma boa ideia encapsular esse algoritmo dentro da classe e, em seguida, usar o polimorfismo para abstrair a chamada ao código.
Vejamos um exemplo prático que mostra como podemos fazer isso usando um exemplo simples em um projeto Console com o .NET 5.0.
Neste exemplo vamos usar a instrução switch/case.
Vamos supor que temos uma classe Vendedor e uma Enumeração definindo 3 níveis de experiência: Junior, Master e Senior:
public enum Experiencia
{
Junior = 1,
Master = 2,
Senior = 3
}
public class Vendedor
{
public string Nome { get; }
public int Vendas { get; }
public Experiencia Experiencia { get; }
public Vendedor(string nome, int vendas, Experiencia experiencia)
{
Nome = nome;
Vendas = vendas;
Experiencia = experiencia;
}
}
|
Agora suponha que com base na experiência do vendedor precisamos calcular as metas de vendas, e para isso criamos a classe CalculaMetaVendas:
public class CalculaMetaVendas { public static int CalculaVendas(Vendedor vendedor) { switch (vendedor.Experiencia) { case Experiencia.Junior: return 5; case Experiencia.Master: return 10; case Experiencia.Senior: return 20; default: throw new ArgumentOutOfRangeException(); } } } |
Estou simplificando a lógica do cálculo das metas de vendas mas em geral aqui teríamos uma lógica mais complexa.
Agora podemos usar os artefatos criados e verificar o seu funcionamento:
class Program { static void Main(string[] args) { var vendedor = new Vendedor("Macoratti", vendas: 5, experiencia: Experiencia.Junior); var metaVendas = CalculaMetaVendas.CalculaVendas(vendedor); Console.WriteLine($"Vendedor = {vendedor.Nome} Vendas = {vendedor.Vendas} Experiência ={vendedor.Experiencia}");
Console.WriteLine($"Meta de Vendas = {metaVendas}");
}
}
|
Esta tudo funcionando mas temos um problema com o bloco switch/case usado para calcular as metas de vendas.
Se houver a introdução de um novo requisito para o cálculo de vendas vamos ter que alterar a instrução switch e o código vai aumentar e teremos mais código procedural sendo adicionado.
Vamos supor que no nosso caso o cálculo das metas de vendas foi alterado para levar em conta também as vendas do vendedor.
public class CalculaMetaVendas { public static int CalculaVendas(Vendedor vendedor) { switch (vendedor.Experiencia) { case Experiencia.Junior: if (vendedor.Vendas < 10) return 10; else return 20; case Experiencia.Master: if (vendedor.Vendas < 20) return 20; else return 30; case Experiencia.Senior: if (vendedor.Vendas < 30) return 30; else return 40; default: throw new ArgumentOutOfRangeException(); } } } |
O código ficou mais complexo e mais difícil de entender, de manter e de testar.
No entanto podemos melhorar o código substituindo as instruções switch/case condicionais usando o polimorfismo. Vejamos como fazer isso.
Substituindo switch/case condicional por polimorfismo
Vamos usar o recurso do polimorfismo e da herança, dois pilares do paradigma da programação orientada a objetos para tornar nosso código mais robusto.
Vamos iniciar refatorando a classe Vendedor:
public abstract class Vendedor { public string Name { get; } public int Vendas { get; } protected Vendedor(string nome, int vendas)
{
Name = name;
Vendas = vendas;
}
}
|
Nossa classe Vendedor agora é uma classe abstrata e vai servir como uma classe base não podendo mais ser instanciada.
A seguir vamos refatorar o código da classe CalculaMetaVendas para remover o switch/case.
Vamos criar a interface ICalculaMetaVendas onde vamos definir o método Calcular() que vai calcular a meta de vendas com base no vendedor.
public interface ICalculaMetaVendas { int Calcular(Vendedor vendedor); } |
Vamos agora criar 3 classes, uma para cada tipo de vendedor:
Essas classes devem herdar da classe base Vendedor e implementar o cálculo da meta de vendas usando a interface ICalculaMetaVendas que definimos com base no tipo de vendedor. Este método vai para o método calcular do tipo da interface a instância do vendedor atual:
1- VendedorJunior
public class VendedorJunior : Vendedor { public VendedorJunior(string nome, int vendas) : base(nome, vendas) { } public int CalculaMetaVendas(ICalculaMetaVendas calcularMeta)
=> calcularMeta.Calcular(this);
}
|
A seguir vamos criar a classe VendedorJuniorCalculaMeta que calcula efetivamente a meta para o tipo de vendedor implementando a interface ICalculaMetaVendas :
public class VendedorJuniorCalculaMeta : ICalculaMetaVendas
{
public int Calcular(Vendedor vendedor)
=> vendedor.Vendas < 10 ? 10 : 20;
}
|
Agora basta repetir o procedimento para VendedorMaster e VendedorSenior. (fica como exercício...)
Vamos testar a nossa implementação:
class Program { static void Main(string[] args) { var vendedor = new VendedorJunior("Macoratti", vendas: 5); var meta = new VendedorJuniorCalculaMeta(); var metaVendas = vendedor.CalculaMetaVendas(meta); Console.WriteLine($"Vendedor = {vendedor.Nome} Vendas = {vendedor.Vendas}");
Console.WriteLine($"Meta de Vendas = {metaVendas}");
Console.ReadKey();
}
}
|
Assim temos a solução que substituiu a utilização do bloco swtich/case usando polimorfismo.
Com esta solução, agora estamos livres para criar novas classes que implementam a interface ICalculaMetaVendas . Quando novos requisitos foram necessários, podemos escrever novas classes focadas, em vez de escrever mais lógica no bloco switch. Assim estaremos aderente ao princípio OCP (Aberto/Fechado).
Observe que a
enumeração também foi substituída por objetos. Agora podemos apresentar classes
como VendedoJunior, VendedorMaster e VendedorSenior
, e, temos assim comportamento e dados agora reunidos em uma classe coesa.
Pegue o projeto aqui:
CShp_UsandoPolimorfismo.zip
"17 Quando
o vi (Jesus), caí aos seus pés como morto. Então ele colocou sua mão
direita sobre mim e disse: "Não tenha medo. Eu sou o primeiro e o último. 18
Sou aquele que vive. Estive morto mas agora estou vivo para todo o sempre! E
tenho as chaves da morte e do Hades."
Apocalipse 1:17,18
Referências:
C# - Herança, Polimorfismo, Sobrescrita e ..
C# - Classes Abstratas, Interface e Polimorfismo
C# - Polimorfismo
C# - Aplicando conceitos OOP e Polimorfismo na prática