terça-feira, 29 de setembro de 2009

Como refatorar um sistema para melhor aproveitar recursos de POO – Parte 2

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.