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.