Na
primeira parte mostrei o código original da aplicação e os seus problemas, agora vamos botar a mão na massa. Vamos refatorar esse programa, passo-a-passo, identificando os pontos onde podemos melhorá-lo.
Separar as responsabilidades
Como vimos anteriormente, nossa classe tem três responsabilidades distintas: ler as notas, calcular a média e exibir o resultado.
O primeiro passo é separar cada responsabilidade em uma classe distinta. Vamos apenas recortar o trecho de código correspondente e colocá-lo em uma nova classe.
Começamos separando a leitura de notas:
public class LeitorNotas
{
public List<decimal> LeNotas()
{
List<decimal> notas = new List<decimal>();
bool continuaLeitura = true;
while (continuaLeitura)
{
Console.Write("Nota (Digite 'S' para sair): ");
string valorDigitado = Console.ReadLine();
if (valorDigitado.Equals("S", StringComparison.CurrentCultureIgnoreCase))
{
continuaLeitura = false;
}
else
{
decimal nota = decimal.Parse(valorDigitado);
notas.Add(nota);
}
}
return notas;
}
}
E depois a exibição da média:
public class MostradorMedia
{
public void ExibeMedia(decimal media)
{
Console.WriteLine("Média: "+ media);
}
}
Nesse momento, a classe
ProcessaMedia deve estar assim:
public class ProcessaMedia
{
public void Executa()
{
LeitorNotas leitorNotas = new LeitorNotas();
List<decimal> notas = leitorNotas.LeNotas();
//RESTO DO CÓDIGO AQUI
MostradorMedia mostradorMedia = new MostradorMedia();
mostradorMedia.ExibeMedia(media);
}
}
Dependendo de uma abstração
O próximo passo é alterar o código para depender de uma abstração e não de uma implementação.
Nesse caso, vamos criar a interface
ILeituraNotas e a
IMostradorMedia, que serão implementadas respectivamente pela classe
LeituraNotas e
MostradorMedia.
public interface ILeitorNotas
{
List<decimal> LeNotas();
}
public class LeitorNotas : ILeitorNotas
{
public List<decimal> LeNotas()
{
//RESTO DO CÓDIGO AQUI
}
}
public interface IMostradorMedia
{
void ExibeMedia(decimal media);
}
public class MostradorMedia : IMostradorMedia
{
public void ExibeMedia(decimal media)
{
Console.WriteLine("Média: "+ media);
}
}
A classe
ProcessaMedia criava o
LeitorNotas e
MostradorMedia, agora ela não fará mais isso. Ela irá receber no construtor um objeto que implemente a interface
ILeitorNotas e um objeto que implemente
IMostradorMedia.
public class ProcessaMedia
{
private ILeitorNotas leitorNotas;
private IMostradorMedia mostradorMedia;
public ProcessaMedia(ILeitorNotas leitorNotas, IMostradorMedia mostradorMedia)
{
this.leitorNotas = leitorNotas;
this.mostradorMedia = mostradorMedia;
}
public void Executa()
{
List<decimal> notas = leitorNotas.LeNotas();
//RESTO DO CÓDIGO AQUI
mostradorMedia.ExibeMedia(media);
}
}
Ao criar o objeto
ProcessaMedia passamos para o construtor os objetos
LeitorNotas e
MostradorMedia.
Também poderíamos utilizar um framework de injeção de dependência, onde ele seria responsável por criar esses objetos auxiliares. Em um futuro post entro em mais detalhes a respeito disso.
Extendendo a funcionalidade
À partir desse momento o processamento da média não depende mais diretamente da leitura no console. Se necessário, podemos criar uma classe que implemente
ILeitorNotas lendo os dados de um arquivo, por exemplo.
Um dos princípios da orientação a objetos diz que
“uma classe deve estar aberta para extensão e fechada para modificação”. No exemplo, a classe
ProcessaMedia está “aberta para extensões” (podemos extendê-la por meio das interfaces criadas) e “fechada para modificações” (a única razão dela ser alterada é se a regra de negócio for alterada).
Finalizando
Obviamente, esse exemplo é bastante simples e temos a sensação de “muita complexidade para pouca necessidade”. Mas repare bem, o que aumentou foi o número de classes, a complexidade foi reduzida. Cada classe tem sua responsabilidade bem definida e clara.
Espero que eu tenha conseguido mostrar o objetivo dessa refatoração. Como você pode ver, é bastante simples e provê várias vantagens.