Mostrando postagens com marcador POO. Mostrar todas as postagens
Mostrando postagens com marcador POO. Mostrar todas as postagens

quarta-feira, 28 de outubro de 2009

A teoria e prática caminham juntas

Frequentemente ouço alguém pedir: "devo aprender primeiro a tecnologia X ou Y?" ou, "o que é melhor eu conhecer, Java ou C# ?". Normalmente minha resposta vai ser: não importa qual você aprenda, desde que aprenda primeiro os conceitos por trás delas.

A importância da teoria

As formas de ensino as quais estamos acostumados normalmente separam a teoria da prática, nunca mostrando como aplicar toda a teoria no mundo real. A teoria e prática são separadas de tal modo que somos levados a crer que uma não tem relação com a outra.

Certamente você já ouvir frases do tipo "Isso é coisa de acadêmico", "No mundo real uma coisa dessas não se aplica" ou então "Eu sou uma pessoa pragmática não ligo pra essas teorias".

Essas frases são exemplos claros de como a teoria e prática são vistas como duas coisas distintas. Uma pessoa se diz pragmática e se afasta de qualquer teoria. A verdade é que a teoria está pros traz de tudo que fazemos, querendo ou não, ela é o fundamento.

O foco excessivo na tecnologia

Uma tendência que vemos atualmente é um foco excessivo na tecnologia em si e não nas motivações por traz dela. Todos querem estudar a nova ferramenta da moda.  As soluções são baseadas apenas em ferramentas.

O que adianta conhecer o framework de desenvolvimento web XYZ se a pessoa não sabe os conceitos básicos de desenvolvimento web? Ou aprender o lindo e maravilhoso framework de persistência recém lançado, sem saber quais seus objetivos e razões de existir.

Esse foco excessivo leva as pessoas a serem especialistas em uma determinada ferramenta, sendo incapazes de utilizar outras ferramentas de mesmo propósito ou até mesmo de fazer qualquer julgamento a respeito delas.

A teoria faz parte da prática

Sempre que preciso utilizar uma ferramenta procuro entender os problemas que motivaram o desenvolvimento dela e a forma que ela visa resolver esses problemas. Com isso em mente, fica fácil comparar diversas ferramentas e adotar outra se necessário.

Um exemplo prático é o desenvolvimento Web. Comecei a desenvolver sistemas web utilizando ASP.Net, e como todos devem saber, o conceito de WebForms adotado pelo ASP.Net tenta abstrair a complexidade da web e simular o desenvolvimento desktop. Nunca gostei muito dessa idéia de abstrair a web, por isso acabava estudando o funcionamento por traz daquela abstração. É importante saber como mostrar os dados em um Grid ou como utilizar um Repeater com dados aninhados, mas tem coisas muito mais importantes que isso.

Com o conhecimento de desenvolvimento web em geral que adquiri, e não apenas o conhecimento da abstração criada pelo ASP.Net, fui capaz de utilizar outros frameworks sem problemas. É só uma questão de traduzir a teoria para os comandos do novo framework.

Orientação a objetos na teoria e na prática

A orientação a objetos não é nova, porém somente na última década ela passou a ser amplamente utilizada.

Em qualquer projeto desenvolvido hoje, se alguém cogitar utilizar o desenvolvimento estruturado, certamente será motivo de piadas. Afinal, programação orientada a objetos (POO) é um paradigma muito superior a programação estruturada. Há apenas um problema: grande parte dos projetos que atualmente são desenvolvidos utilizando POO não utilizam os conceitos de POO, são apenas programas estruturados utilizando classes.

POO não se resume a conhecer o que é classe, objeto, método e herança. Há muitos outros princípios que se aplicados ajudam no bom desenvolvimento. Os princípios mais difundidos são os que conhecemos pelo acrônimo SOLID. São eles:
  • SRP - Single Responsibility Principle;
  • OCP - Open Closed Principle;
  • LSP - Liskov Substitution Principle;
  • ISP - Interface Segregation Principle;
  • DIP - Dependency Inversion Principle.
Apenas com estes princípios é que podemos realmente alcançar todo o potencial da POO e não apenas desenvolver programas estruturados utilizando classes. Em futuros posts devo falar mais sobre eles.
Mais uma vez, é um questão de mudar o foco da tecnologia para a teoria.

Em resumo

Para ter sucesso no desenvolvimento de software devemos tirar um pouco o foco da tecnologia e aprender os conceitos por traz dela. É uma pena que a cada dia as instituições de ensino estão focando menos nessa teoria e mesmo quando o fazem, é de forma distante da realidade, ficando difícil fazer uma ligação entre teoria e prática.

quarta-feira, 30 de setembro de 2009

Como refatorar um sistema para melhor aproveitar recursos de POO – Final

Nos dois últimos posts mostrei o código original do programa, e os passos para refatorá-lo. Leia a Parte 1 e Parte 2 se você ainda não leu. Para finalizar, quero mostrar como podemos extendê-lo de forma simples.

Imagine que agora, ao invés de ler do console, queremos ler as notas de um arquivo e salvar a média em outro. Para isso, não vamos alterar o código existente, vamos apenas criar duas classes. Uma implementando a interface ILeitorNotas e outra a interface IMostradorMedia.

Veja o código abaixo:
public class LeitorNotasEmArquivo : ILeitorNotas
{
  private string filename;

  public LeitorNotasEmArquivo(string filename)
  {
    this.filename = filename;
  }

  public List<decimal> LeNotas()
  {
    string[] valoresNoArquivo = File.ReadAllLines(filename);

    List<decimal> notas = new List<decimal>();
    foreach (string valor in valoresNoArquivo)
    {
      decimal nota = decimal.Parse(valor);
      notas.Add(nota);
    }

    return notas;
  }
}

public class MostradorMediaEmArquivo : IMostradorMedia
{
  private string filename;

  public MostradorMediaEmArquivo(string filename)
  {
    this.filename = filename;
  }

  public void ExibeMedia(decimal media)
  {
    File.WriteAllText(filename, media.ToString());
  }
}

A chamada do ProcessaMedia ficaria assim:
static void Main(string[] args)
{
  ProcessaMedia processaMedia = new ProcessaMedia(
    new LeitorNotasEmArquivo("D:\\notas.txt"), 
    new MostradorMediaEmArquivo("D:\\media.txt")
  );
  processaMedia.Executa();

  Console.ReadKey();
}

Apenas lembrando que esse código é apenas para fins de exemplo, ele não tem nenhum tratamento adicional para deixá-lo simples.

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.

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

Atualmente a programação orientada a objetos (POO) é o paradigma predominante no desenvolvimento de sistemas. Porém, apesar de todos utilizarem e saberem como funciona, nem sempre o código desenvolvido realmente utiliza o potencial da POO.

É bastante comum encontrarmos sistemas desenvolvidos com orientação a objetos mas que não passam de programas estruturados utilizando classes.

Tentarei mostrar aqui um exemplo simples de como refatorar um código utilizando alguns princípios da orientação a objetos.

O código original

O exemplo que utilizarei aqui é bastante simples, não quero me ater no programa em si, mas nos conceitos utilizados. Então, não leve muito em consideração o programa, veja o conceito para poder utilizá-lo em qualquer outro caso.

Segue abaixo o código original do programa. Como você pode notar, ele simplesmente lê várias notas, calcula a média e a exibe.

class Program
{
  static void Main(string[] args)
  {
    ProcessaMedia processaMedia = new ProcessaMedia();
    processaMedia.Executa();

    Console.ReadKey();
  }
}


public class ProcessaMedia
{
  public void Executa()
  {
    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);
      }
    }

    decimal totalNotas = 0;
    int qtdNotas = notas.Count;
    foreach (decimal nota in notas)
    {
      totalNotas += nota;
    }

    decimal media = 0;
    if (qtdNotas > 0)
    {
        media = totalNotas/qtdNotas;
    }

    Console.WriteLine("Média: "+ media);
  }
}

Quais os problemas nesse código?

Quais os problemas nesse código e o que podemos fazer para melhorá-lo? Vejamos:
  1. A classe ProcessaMedia tem três responsabilidades. Ela é responsável por lêr a nota, calcular a média e exibir o resultado. O ideal é que uma classe tenha apenas uma responsabilidade;
  2. Ela está diretamente acoplada ao Console. Isso pode gerar alguns problemas, por exemplo, como você faria um teste unitário dessa função? Simplesmente não é possível.
  3. Ela não é extensível. Se, por exemplo, surgir a necessidade de dar a opção de ler as notas de um arquivo, como você faria? No código seriam feitos uma série de condições IF?
Para resolver esses problemas vamos adotar algumas medidas:
  1. Separar cada responsabilidade em uma classe;
  2. A classe CalculaMedia deverá depender de uma abstração, e não do Console;
  3. Com a implementação dos itens 1 e 2 automaticamente ganhamos a extensibilidade, nenhuma medida adicional será necessária.

Mãos na massa

No próximo post mostrarei os passos para refatorar esse código.

Apesar deste ser um exemplo simples, podemos aplicar os mesmos conceitos em muitas outras situações. Pense um pouco e você logo vai lembrar de situações semelhantes a esse exemplo.