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.