Idéias sobre encapsulamento, linq e delegates em C#
Atualmente estou trabalhando em projeto .Net. É a primeira vez que utilizo a plataforma profissionalmente e não dá para negar o quanto a .Net está interessante. Estou bastante impressionado com a linguagem C#. Apesar de todos os recursos interesantes da linguagem, tenho percebido que está acontecendo uma espécie de “relaxamento” em relação as boas práticas, principalmente de orientação a objetos. Umas coisas que mais tenho notado é a falta de preocupação com o encapsulamento, principalmente quando se usa delegates e linq.
Neste post vou analisar alguns pontos relacionados a encapsulamento e o uso do Linq.
Vamos começar a analisar um modelo de objetos simples:
Como podemos perceber uma empresa tem muitos funcionários e estas classes poderiam ser escritas desta forma:
class Funcionario { public string Nome { get; set; } public string Cargo { get; set; } public double Salario { get; set; } public bool Ativo { get; set; } } class Empresa { public List Funcionarios {get; set;} public Empresa() { Funcionarios = new List(); } }
Aqui um exemplo de como utilizar as classes deste modelo:
Funcionario luiz = new Funcionario() { Ativo = true, Nome = "Luiz", Salario = 1000, Cargo = "Desenvolvedor" }; Funcionario sergio = new Funcionario() { Ativo = true, Nome = "Sergim", Salario = 1000, Cargo = "Desenvolvedor" }; Funcionario iuri = new Funcionario() { Ativo = true, Nome = "Iuri", Salario = 1100, Cargo = "Desenvolvedor" }; Funcionario luciano = new Funcionario() { Ativo = false, Nome = "Luciano", Salario = 2000, Cargo = "Arquiteto" }; Funcionario eduardo = new Funcionario() { Ativo = true, Nome = "Eduardo", Salario = 3000, Cargo = "Gerente" }; // adicionando funcionários na empresa. Empresa google = new Empresa(); google.Funcionarios.Add(luiz); google.Funcionarios.Add(iuri); google.Funcionarios.Add(luciano); google.Funcionarios.Add(sergio);
Identificando falhas no design
Mesmo com um design simples como este um problema básico de design aparece. A classe empresa expõe o atributo Funcionário através de uma property e assim viola o encapsulamento. Mas uma property não encapsula o atributo, através de gets e sets? A resposta é que neste caso, o atributo está escondido, mas eu tenho acesso a referência diretamente e o resultado disso é que qualquer “cliente” que usar o objeto do tipo empresa, pode manipular a lista de funcionários diretamente, ignorando quaisquer regras que por ventura existam. Por exemplo imagine a situação onde quisessemos que nunca um Funcionario fosse removido. Do jeito que está qualquer um poderia fazer google.Funcionarios.Remove(sergio).
Um outro problema que aparece nesse código é que temos um conceito de negócio sendo manipulado, mas não explícito. Quando fazemos google.Funcionarios.add(luiz), isso representa uma ação no nosso domínio. Na verdade o que queremos fazer é contratar o Luiz. Técnicas como Domain Driven Design, pregam que nosso código deve falar a lingua do negócio. De acordo com DDD, precisamos tornar este conceito explícito em nosso código. Vamos alterar a classe empresa para suportar isso:
class Empresa { public List Funcionarios {get; set;} public Empresa() { Funcionarios = new List(); } public void Contrata(Funcionario funcionario) { this.Funcionarios.Add(funcionario); } }
Repare que agora, existe um método na classe empresa que representa o conceito que estava implícito. Sempre devemos tornar os conceitos implícitos explícitos. Agora nosso código fica muito mais OO, se é que se pode usar esta expressão:
google.Contrata(eduardo); google.Contrata(luiz); google.Contrata(iuri); google.Contrata(luciano); google.Contrata(sergio);
Agora o conceito “contratação” está explícito, mas ainda existe um problema, a classe empresa, ainda tem uma property que te permite acessar a lista de funcionários. Apesar de ter criado o método contrata, isso não garante que a lista não será manipulada diretamente.
google.Funcionarios.Remove(luiz);
Este trecho de código mostra novamente o problema de expor a lista, neste caso ao invés de contratar um funcionário estamos demitindo. Agora imagine que existe uma regra que especifica que todo funcinário demitido tem que ser inativado, como vamos garantir que isto vai ser feito? Podemos inativar o funcionário antes de removê-lo da lista:
luiz.Inativar(); google.Funcionarios.Remove(luiz);
O problema desta abordagem é óbvio, não tem como garantir que esta regra será executada toda vez que precisarmos demitir um funcionário. Para resolver isso, novamente vamos tornar explícito o conceito de demissão.
class Empresa { private List Funcionarios; public Empresa() { Funcionarios = new List(); } public void Contrata(Funcionario funcionario) { this.Funcionarios.Add(funcionario); } public void Demite(Funcionario funcionario) { funcionario.Inativar(); this.Funcionarios.Remove(funcionario); } }
Agora a classe Empresa tem um método Demite, o que torna explícito o conceito demissão. Note que antes de executar a remoção do usuário, fazemos a sua inativação. Além disso, não existe mais a property na classe empresa que expõe o atributo funcionários, este atributo é privado e somente a classe empresa sabe como manipulá-lo de forma correta.
Não fizemos nada além de seguir princípios básicos de OO aplicando o encapsulamento, e neste pequeno exemplo mostramos como é possível tornar o código mais fácil de se manter utilizando esta técnica.
Encapsulamento e linq no C#
Nas últimas versões do C# várias coisas interessantes foram incluídas, tornando a linguagem muito poderosa. Mas por outro lado, tenho visto uso destas técnicas de forma não muito organizada, principalmente em relação as boas práticas de programação em uma linguagem Orientada a Objetos.
Um dos recursos mais interessantes e que utiliza muitas das novas características da linguagem é o Linq. Vamos dar uma olhada no que é possível fazer com o Linq:
var nomes = new string[] {"luiz","sergio", "iuri", "eduardo", "jose maria"}; var nomesQueComecamComS = nomes.Where(nome => nome.StartsWith("S")); var nomesQueContemMaria = nomes.Where(nome => nome.Contains("maria")); string[] cities = new string[] { "Auckland", "Oslo", "Sydney","Seattle", "Paris", "Los Angeles" }; IEnumerable places = from city in cities where city.Length > orderby city descending select city;
Isso é muito poderoso, podemos fazer operações sobre lista de forma muito mais declarativa. O problema é que todo esse poder pode esconder algumas surpresas se não tomarmos os devidos cuidados com sua utilização.
No nosso primeiro exemplo, vimos como encapsulamento na sua forma mais simples, torna nosso código mais simples, expressivo e de mais fácil manutenção.
Vamos voltar a analisar o primeiro exemplo, só que pela perspectiva do uso do linq. Para isso vamos precisar colocar criar novamente a property funcionários na classe empresa.
class Empresa { public List Funcionarios {get;set;} }
Considere os seguintes trechos de códigos, e veja como é possível trabalhar de forma declarativa utilizando os recursos do linq:
var funcionarios = google.Funcionarios; var salarioMedido = funcionarios.Average(f => f.Salario); Console.WriteLine(salarioMedido); var funcionariosAtivos = funcionarios.Where(f => f.Ativo == true); var desenvolvedores = funcionarios.Where(f => f.Cargo.Equals("desenvolvedor")); var GerenteComSalarioMaior2000 = funcionarios.Where(f => f.Cargo.Equals("Gerente") & f.Salario > 2000);
Veja que fizemos várias operações sobre a lista de funcionários da empresa. A primeira vista isso é muito prático, mas podemos ter problemas com essa abordagem. Na minha opinião o maior problema disso é que perdemos o conhecimento fundamental do negócio. Se eu quero saber o salário médio dos funcionários de uma empresa, então esta operaçao deve estar explícita no objeto empresa.
Mas se eu fizer isso vou deixar de tirar proveito das facilidaes do Linq? Não, basta promover o encapsulamento e mover este código para a classe empresa. A partir de agora o usuário da classe vai passar a invocar o método disponível no objeto empresa:
salarioMedido = google.ObterSalarioMedio();
Após fazer a mudança, o código fica mais simples, intuitivo e expressivo. O leitor do código, entende rapidamente o que esta linha de código faz e não precisa saber detalhes de implementação.
Outro exemplo de código é a linha :
var funcionariosAtivos = funcionarios.Where(f => f.Ativo == true);
Neste trecho temos 2 problemas. Novamente a falta de encapsulamento. De quem é a responsabilidade de dizer quem são os funcionários ativos? Deveria ser da classe empresa, então precisamos criar um novo método na classe para obter os funcionários ativos.
Então na classe empresa temos:
public IEnumerablel ObterFuncionariosAtivos() { return this.Funcionarios.Where(f => f.Ativo == true); }
O segundo problema desta expressão novamente é falta de encapsulamento, estamos violando o princípio Tell don’t Ask quando fazemos f.ativo == true. Quem sabe dizer se o funcionário é ativo ou não é o objeto funcionário, então é necessário criar o método na classe funcionário para tornar o explícito o conceito:
class Funcionario { public string Nome { get; set; } public string Cargo { get; set; } public double Salario { get; set; } public bool Ativo { get; set; } public bool EstahAtivo() { return this.ativo == true; } }
E na classe empresa podemos alterar a expressão Linq para utilizar o método da classe funcionário.
public IEnumerable ObterFuncionariosAtivos() { return this.Funcionarios.Where(f => f.EstahAtivo()); }
Aqui a classe empresa completa:
class Empresa { private List Funcionarios; public Empresa() { Funcionarios = new List(); } public void Contrata(Funcionario funcionario) { this.Funcionarios.Add(funcionario); } public void Demite(Funcionario funcionario) { funcionario.Inativar(); this.Funcionarios.Remove(funcionario); } public IEnumerablel ObterFuncionariosAtivos() { return this.Funcionarios.Where(f => f.Ativo == true); } public double ObterSalarioMedio() { return this.Funcionarios.Average(f => f.Salario); } }
Estes são apenas alguns exemplos de código simples onde a falta de encapsulamento pode trazer problemas no futuro. Com certeza um dos mais evidentes é a replicação de código e consequentemente os problemas de manutenção causados por ele, além é claro do fato do conhecimento do domínio ficar espalhado entre várias expressões Linq.
Então os problemas são das novas features do C#?
Não, com certeza isso não é problema das novas features do c#. A questão evidente é que muitos desenvolvedores estão esquecendo princípios básicos ao utilizarem estes recursos.
Existem alguns artigos na web falando sobre a tendência de se misturar os paradigmas funcional e OO. Nós como desenvolvedores só temos a ganhar com isso, pois podemos tirar proveito do melhor dos dois paradigmas. O que não pode acontecer é voltarmos aos problemas que tínhamos no passado, geralmente por descuido ou simplesmente por um “relaxamento” com princípios básicos de Orientação Objetos.
até a próxima,
Luiz Costa


May 17th, 2010 - 00:17
Muito bom o artigo…
Realmente não adianta várias funcionalidades e possibilidades de novas ferramentas… se no final ao implementa-las esquecermos de respeitar as boas práticas…
July 8th, 2010 - 10:51
Olá.
No primeiro exemplo, você poderia utilizar
public List Funcionarios {get; private set;}
a fim de que fosse possível recuperar a lista de funcionários. Isto aproveita melhor os recursos da linguagem C#.
Se você fez que nem em Java (como abaixo), você praticamente dispensou o uso das propriedades automáticas do C# 3.0.
private List Funcionarios;
public List GetFuncionarios(){
return this.Funcionarios;
}
Quanto a parte de encapsulamento e Linq, ficou muito bom. É isso mesmo! Parabéns!
July 8th, 2010 - 22:09
Olá Orlando,
Obrigado pelo seu comentário.
Neste caso, a lista de funcionários privada é proposital. Não é interessante, neste contexto, deixar obter uma referência para lista de funcionários, isto vai quebrar o encapsulamento. O meu ponto é que se esta referência estiver disponível, tem que se ter muito cuidado para não fazer operações sobre a lista que deveriam ser da classe Empresa. Tenho visto alguns projetos ignorando este detalhe e tendo uma duplicação absurda de código.
Mas você tem razão, as properties do C# são uma mão na roda mesmo. Só tem que tomar cuidado com o encapsulamento.
Até
Luiz Costa
July 30th, 2010 - 11:03
Cara Muito bom artigo, gostei abração!!!!!
July 30th, 2010 - 14:39
Se você estivesse usando Generics no get você poderia fazer isso.
private List funcionarios;
public List Funcionarios
{
get { return funcionarios.AsReadOnly(); }
}
July 30th, 2010 - 14:54
Legal Luiz, muito bom o teu artigo.
Evidente que o modelo rico aumenta a coesão e melhora muito a leitura, ao contrário do modelo anêmico. Já vi muitos casos semelhantes ao que falaste, como (em Java) getFuncionarios().add(funcionario), isto dentro de um objeto de negócio (no estilo Transaction Script, como um EJB).
Sou iniciante na plataforma .NET, tenho muito mais background em Java, mas estes conceitos são crosslanguages e crossplatforms, afinal, é design.
Um abraço.
August 3rd, 2010 - 20:37
Excelente Artigo, Parabéns!
so fiquei com uma duvida, se a propriedade Ativo do Funcionário esta Private no ultimo Diagrama de Classe, como é que se faz para pode realizar com sucesso o metodo de Extensão funcionario.Inativar() no Metodo Demite() da classe Empresa?
Necessito saber isso, pois estou na busca de informações de como fazer um bom Design já que não sei trabalhar OOP e so faço modelos anêmicos por falta de dominio de modelagem/Design.
Por falar nisso, o que vocês indicam para quem quer aprender mais sobre modelagem/Desing?
Hoje só consigo pensar em Tabelas e querys que vão realizar alguma modificação no Banco. Uma limitação de quem não domina OOP e Design. Este sou eu…!!
[]´s
Edmilson
August 4th, 2010 - 00:46
@Rodrigo
Oi rodrigo, é verdade, seria interessante deixar a lista read only, mas isso não evita a violação do encapsulamento causado pelo uso descuidado das queries do linq.
@marcio
Obrigado pelo comentário. A arquitetura antiga do j2ee realmente influenciou bastante a comunidade java. Mas isso tem mudado bastante. Mas acho que isso não é exclusividade do java, em .net também temos vários exemplos de design questionáveis. Mas como vc mesmo disse, design é design.
August 4th, 2010 - 00:58
Oi Edmilson,
Obrigado pelo comentário.
Não tenho muita certeza do que vc quer dizer com “Método de Extensão”, mas o fato é que tem um erro no diagrama, falta colocar público o método Inativar na classe funcionário. O atributo de “ativo” de funcionário realmente é privado, mas este é manipulado apenas através de métodos, neste caso o método “Inativar”. Na classe empresa, é possível acessar este método normalmente pq ele é público na classe funcionário.
Um bom ponto de partida para entender um pouco sobre projeto orientado a objetos é o livro do Craig Larman segue o link
http://www.amazon.com/Applying-UML-Patterns-Introduction-Object-Oriented/dp/0131489062/ref=sr_1_1?ie=UTF8&s=books&qid=1280894086&sr=8-1.
É uma excelente introdução e tem uma versão em português também.
Nós escrevemos um post aqui no saga ano passado sobre modelo de objetos e modelo de dados, talvez possa te ajudar.
Segue o link
http://www.sagadoprogramador.com.br/2009/07/nosso-modelo-de-objetos-nao-deve-ser-uma-copia-do-nosso-modelo-de-dados/
Um abraço,
Luiz Costa