9 de fev. de 2011

Paradigmas, Coleções, Java, Programação Funcional, C# e Ruby

São vários os paradigmas de programação, entretanto os mais conhecidos e abordados são imperativo, estruturado, procedural e orientado a objetos (OOP). Os quais são suportados por muitas linguagens conhecidas como PHP, Java, Visual Basic, etc. Outros que tem destaque no desenvolvimento de aplicações são o funcional e orientado a aspectos (AOP).

Vou usar um estudo de caso simples nesse post para contrastar:
Imagine uma coleção de candidatos, com apenas dois atributos: nome e idade. Após obter esta lista, de um base de dados ou integração, é necessário subdividi-la em duas: candidatos maiores de idade e menores de idade. Preferencialmente deve ser uma solução reusável.

Abaixo fiz uma implementação em Java. Existem várias estratégias de implementação para obter os menores e maiores, qual seria a sua?

Arquivo Main.java
import java.util.*;

public class Main {

 public static void main(String[] args) {
  
  List<Candidato> candidatos = getCandidatosFromDataSource();
  
  //List<Candidato> menores = ??;
  //List<Candidato> maiores = ??;
  
 }
 
 
 public static List<Candidato> getCandidatosFromDataSource() {
  List<Candidato> candidatos = new ArrayList<Candidato>();
  
  candidatos.add(new Candidato("Alvaro", 23));
  candidatos.add(new Candidato("Vanessa", 13));
  candidatos.add(new Candidato("Paulo", 54));
  candidatos.add(new Candidato("André", 17));
  candidatos.add(new Candidato("Renata", 32));
  
  return candidatos;
 }
  
}

class Candidato {
 
 public String nome;
 
 public int idade; 
 
 public Candidato(String nome, int idade) {
  this.nome = nome; this.idade = idade;
 }

 @Override
 public String toString() {
  return "Candidato [nome=" + nome + ", idade=" + idade + "]";
 }
 
}



Para ilustrar vou extrair os menores de idade com algumas estratégias em Java e depois usando paradigma funcional em C#.


Java - exemplo 1 - usando lista temporária para adicionar os candidatos que aderem a regra:

public static void main(String[] args) {
 
 List<Candidato> candidatos = getCandidatosFromDataSource();
 
 List<Candidato> menores = menoresDeIdade(candidatos);  
 
 System.out.println(menores);
 
}

public static List<Candidato> menoresDeIdade(List<Candidato> candidatos) {
 List<Candidato> menores = new ArrayList<Candidato>();
 for (Candidato c : candidatos) 
  if (c.idade < 18)
   menores.add(c);
 return menores;
}

Este método funciona normalmente como uma implementação procedural. Java - exemplo 2 - usando lista temporária para remover os candidatos que não aderem a regra:

public static void main(String[] args) {
 
 List<Candidato> candidatos = getCandidatosFromDataSource();
 
 List<Candidato> menores = menoresDeIdade(candidatos);  
 
 System.out.println(menores);
 
}

public static List<Candidato> menoresDeIdade(List<Candidato> candidatos) {
 List<Candidato> menores = new ArrayList<Candidato>(candidatos);
 for (Candidato c : menores)
  if (c.idade > 18) menores.remove(c);
 return menores;
}

Esse método FALHA. Não é possível iterar e manipular uma lista simultaneamente. A seguinte exceção será lançada: Exception in thread "main" java.util.ConcurrentModificationException Na verdade, esse é um erro bem comum de programadores Java que não conhecem a API Collections. O método acima funciona se tu usares um ListIterator, que permite percorrer e manipular a lista. Exemplo corrigido:

public static List<Candidato> menoresDeIdade(List<Candidato> candidatos) {
 List<Candidato> menores = new ArrayList<Candidato>(candidatos);
 ListIterator<Candidato> iCandidato = menores.listIterator();
 while (iCandidato.hasNext()) {
  Candidato c = iCandidato.next();
  if (c.idade > 18) 
   iCandidato.remove();
 }  
 return menores;
}

Ok? Dúvidas, comentem.

 Java - exemplo 3 - especializando a coleção de Candidatos:

public static void main(String[] args) {
  
 Candidatos candidatos = new Candidatos(getCandidatosFromDataSource());
 
 System.out.println(candidatos.menoresDeIdade());
 
}


class Candidatos extends ArrayList<Candidato> {
 private static final long serialVersionUID = 0;

 public Candidatos() {
  super();
 }
 
 public Candidatos(Collection c) {
  super(c);
 }
  
 public Candidatos menoresDeIdade() {
  Candidatos menores = new Candidatos();
  for (Candidato c : this) 
   if (c.idade < 18)
    menores.add(c);
  return menores;
 }
}


Particularmente, acho essa solução mais elegante. Ela é orientada a objetos, bem diferente das outras, que chamavam um procedimento passando a lista de Candidatos (procedural). Alguns não gostam por que tem que criar classes, mas o que faz o programador de linguagens orientada a objetos senão modelar e criar classes?

 Dica: Não tenha medo de criar classes.

 O modo C# de fazer 

Assim como em outras linguagens, existem várias estratégias possíveis para fazer esta implementação. Os métodos utilizados em Java também podem ser usados em C#, mas existem duas formas particulares de fazer. Os exemplos a seguir são baseados no código abaixo: 

Arquivo Program.cs

using System;
using System.Collections.Generic;

class Program {

    static void Main(string[] args) {

        List<Candidato> candidatos = getCandidatosFromDataSource();

        // List<Candidato> menores = ??

    }

    public static List<Candidato> getCandidatosFromDataSource() {
        List<Candidato> Candidatos = new List<Candidato>();

        Candidatos.Add(new Candidato { Nome = "Alvaro", Idade = 23 });
        Candidatos.Add(new Candidato { Nome = "Vanessa", Idade = 13 });
        Candidatos.Add(new Candidato { Nome = "Paulo", Idade = 54 });
        Candidatos.Add(new Candidato { Nome = "André", Idade = 17 });
        Candidatos.Add(new Candidato { Nome = "Renata", Idade = 32 });

        return Candidatos;
    }

}

class Candidato {
    public string Nome { get; set; }
    public int Idade { get; set; }

    public override string ToString() {
        return "Candidato[Nome: " + Nome + ", Idade: " + Idade + "]";
    }
}

C# - exemplo 1 - usando yeld:

static void Main(string[] args) {

    List<Candidato> candidatos = getCandidatosFromDataSource();

    foreach(Candidato c in Menor(candidatos))
        Console.WriteLine(c);

}

public static IEnumerable<Candidato> Menor (List<Candidato> Candidatos) {
    foreach (Candidato p in Candidatos)
        if (p.Idade < 18) 
            yield return p;
}

Usando yeld return, é possível acumular os objetos em um IEnumerable, que mais tarde pode ser iterado exatamente como no exemplo.


C# - exemplo 2 - usando extension method, delegate e lambda: 

Tudo começa com esta classe de extensão:

static class Util {

    public static List<Candidato> SomenteSe(this List<Candidato> lista, Criterio crit) {
        List<Candidato> candidatos = new List<Candidato>();
        foreach (Candidato c in lista)
            if (crit(c)) 
                candidatos.Add(c);
        return candidatos;
    }

    public delegate bool Criterio(Candidato c);

}

Com ela posso retornar qualquer sublista de acordo com um Criterio, a definir. Melhor com um exemplo de uso:

static void Main(string[] args) {

    List<Candidato> candidatos = getCandidatosFromDataSource();

    foreach (Candidato c in candidatos.SomenteSe(c => c.Idade < 18))
        Console.WriteLine(c);

}

Neste exemplo o método SomenteSe é uma extensão de List<Candidato>. O parâmetro no parenteses é uma expressão lambda que representa o delegado que avalia o Criterio. É lida mais ou menos assim: para cada candidato c retorna verdadeiro se c.Idade for menor que 18. Essa é uma abordagem funcional, pois eu delego parte do processamento para uma função. 
<Candidato>

<Candidato> Na prática eu nem precisaria ter implementado por que o C# já tem uma API para lidar com coleções chamada LINQ. 
<Candidato>

<Candidato> C# - exemplo 3 - usando o LINQ: 
<Candidato>

 LINQ é o acrônimo de Language Integrated Query. O LINQ é usado para manipular coleções com vários tipos de implementações como: LINQ to Objects, LINQ to XML, LINQ to DataSet, LINQ to SQL, LINQ to Entities, e por aí vai. Cada biblioteca abstrai a manipulação de objetos, elementos ou nodos.

Para usar o LINQ para objetos é necessário declarar o namespace: using System.Linq;

A implementação fica assim:

static void Main(string[] args) {
    
    List<Candidato> candidatos = getCandidatosFromDataSource();

    foreach (Candidato c in candidatos.Where(c => c.Idade < 18))
        Console.WriteLine(c);

}


Se o critério será usado mais de uma vez basta declará-lo:


static void Main(string[] args) {
    
    List<Candidato> candidatos = getCandidatosFromDataSource();
    
    Func<Candidato, bool=""> menores = c => c.Idade < 18;
    
    foreach (Candidato c in candidatos.Where(menores))
        Console.WriteLine(c);

}


Outras linguagens 

 Só para constar, existem outras linguagens que usam o paradigma funcional como Ruby por exemplo. Para ilustrar colei abaixo a mesma funcionalidade feita com Ruby:

Arquivo candidatos.rb

def main
    candidatos = candidatos_from_data_source

    menores = candidatos.select { |c| c.idade < 18 }
    
    puts menores
end

def candidatos_from_data_source
    candidatos = [
        Candidato.new("Alvaro", 23),
        Candidato.new("Vanessa", 13),
        Candidato.new("Paulo", 54),
        Candidato.new("André", 17),
        Candidato.new("Renata", 32)
    ]
    candidatos
end

class Candidato

    attr_accessor :nome, :idade
    
    def initialize nome, idade
        @nome = nome
        @idade = idade
    end
    
    def to_s
        "Candidato[nome: #{@nome}, idade: #{@idade}]"
    end

end

main


Não é a toa que há demanda por programadores multiparadigma. Ainda conversava com o Leonardo esses dias sobre uma vaga em que pediam conhecimento de Haskell, que é uma linguagem puramente funcional. Posso falar mais em outro post/tópico, inclusive sobre Programação Orientada a Aspectos.

Qualquer dúvida, comentem.

Nenhum comentário: