Posts Tagged ‘OO’

Idéias sobre encapsulamento, linq e delegates em C#

Posted in c#, OO on May 5th, 2010 by Luiz Costa – 11 Comments

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