Objetos não são atributos + funções
Quando simplificamos demais ao ensinar conceitos acabamos criando problemas que demoram anos para se resolver, se é que são resolvidos. Um exemplo que eu observei hoje foi como algumas pessoas pensam sobre objetos.
É comum que seja ensinado no primeiro contato das pessoas com orientação a Objetos que objetos representam atributos e funções embaladas no mesmo saco. Bom, isso é uma simplificação extrema utilizada para fazer com que quem conheça programação procedural trabalhe com objetos de maneira mais simples, mas é uma simplificação tão grosseira que atrapalha algumas pessoas para o resto da vida.
Objetos não possuem propriedades + funções, eles possuem estado + comportamento. Você provavelmente vai usar atributos (i.e. variáveis de instancia) e funções (de instancia também) para implementar isso mas é um detalhe de implementação, não algo que deva ser exposto.
Um agravante deste problema são propriedades. Sejam propriedades bem resolvidas como as do C# ou meia-boca como do Java, elas representam apenas mais uma mensagem que o objeto recebe (já que ambas as linguagens são baseadas em troca de mensagens) e não “atributos” do objeto.
get/set em Java são herança de um modelo de componentes. Componentes não são necessariamente objetos, o que a (finada?) especificação JavaBeans fez foi utilizar classes Java para implementar componentes gráficos. Como não havia metadados (annotations em Java) naquela época o melhor era adotar uma convenção, daí os setXxx e getXxx da vida.
O problema é que este idioma saiu do controle. Ao invés de utilizarmos um método que faça sentido dentro do domínio preferimos criar um get/set burro, típico de arquiteturas BOLOVO.
Por exemplo, suponha que você possua uma classe Pedido que possui um status. Quando alguém vai atualizar o status do pedido para “finalizado” qual idioma é mais comum?
//1) set/get pedido.setStatus(Pedido.Status.FINALIZADO); //2) Mensagem pedido.finalizar();
Creio que você concorda comigo que o primeiro é de longe mais utilizado. Qual o problema com ele? Como dissemos antes, Java é uma linguagem baseada em troca de mensagens. Uma mensagem é algo que o objeto recebe e decide o que fazer, no caso de Java isso acontece quando chamamos um método. Quem deve decidir o que deve ser feito não é o cliente e sim o destinatário da mensagem. Quando você diz setStatus(Status) você está dizendo para a classe que quer que os status dela seja o especificado. Pensa bem, é isso que você quer?
O que você quer em verdade é que o pedido seja finalizado. Como isso é feito, se muda status ou não, não é papel do código cliente, é papel do objeto (e deve ser documentado, especificado e verificado através de testes unitários).
Da mesma forma, quando você executa um getStatus() no objeto o que você faz com isso? Exceto em alguns casos específicos, um método getXxx() só deveria ser utilizado porque você precisa executar algo com ele que não vai envolver mudança de estado, como por exemplo exibir numa interface gráfica. Se você faz um getXxx() porque baseado no retorno você chama uma lógica ou outra provavelmente você está fazendo algo errado, já que é uma violação gritante da Lei de Deméter.
O que eu deveria fazer, então? Suponha que eu tenha o seguinte código:
public void notificarMudancaEm(Pedido p){
if(p.getStatus().equals(Pedido.Status.FINALIZADO){
servicoEmail.enviamensagemConclusao(p);
}
}
Isso parece bem razoável, não? A lógica de enviar um e-mail de conclusão não está no Pedido e sim em algum serviço próprio para isso. Ele provavelmente é um observador do pedido e cada vez que você chama setStatus() esta é notificado. Num caso real recente eu estava trabalhando numa aplicação legada com o código exatamente como o acima. De repente alguém decidiu que um Pedido também está finalizado se estiver em status CANCELADO.
Essa é a hora em que o programador xinga a mãe do analista de negócios e vai caçar um email de três meses atrás que contem uma documentação, assinada em sangue, que contraria isso. O gerente de projetos vê aquilo, dá um sorriso e manda um email para o cliente daquela consultoria de três letrinhas dizendo que é uma mudança de escopo e que vai precisar de 50 horas a mais para isso. Ele diz ao programador que na verdade só tem 5 horas porque as outras 45 ele vai usar como desculpa quando o projeto atrasar. De quem é a culpa?
Bom, de quem é a culpa é assunto para outro post mas eu te garanto que não é da Orientação a Objetos. O problema no código acima (e certamente este código está repetido em milhões de outros lugares) é que ele não cumpre o princípio básico de deixar o objeto ser responsável pela sua própria vida. Você precisa saber se o pedido está finalizado mas ao invés de perguntar isso ao objeto você pergunta qual seu status.
Imagine que ao invés do código acima você possuísse algo mais… OO:
public void notificarMudancaEm(Pedido p){
if(p.isFinalizado()){
servicoEmail.enviamensagemConclusao(p);
}
}
E suponha que você tenha que alterar o que FINALIZADO significa. Qual é mais fácil?
É este tipo de coisa que você perde ao transformar objetos em atributos+funções. Ao invés de pensar “quais são os atributos deste objeto?” pense “quais mensagens este objeto responde?”.
May 19th, 2008 at 2:17 am
Shoes, como sempre considerações muito interessantes.
Agora uma pergunta, o que você recomenda fazer quando se utiliza Hibernate?
Quando mapeio uma classe (eu utilizo o método dos arquivos xml) tenho que criar métodos getter e setter para todos os atributos mapeados e na maioria das vezes acabam virando métodos burros ferindo em exagero o encapsulamento.
Qual seria uma boa solução?
May 19th, 2008 at 6:23 am
Oi, Marcos,
Há algum tempo o Hibernate disponibiliza o nível de acesso “field” que perite que você não use get/set. Ainda que pensemos em um outro framework sem este recurso uma coisa é ser obrigado a ter get/set n seu objeto, outra coisa é utilizá-los no seu código.
[]s
May 19th, 2008 at 7:08 am
Muito bom o artigo. Eu adoro este tipo de análise onde se refatora um código supostamente OO, para algo mais elegante.
Você conhece algum livro/blog para indicar que trata deste mesmo assunto?
Pois você está completamente certo sobre em todo lugar ser ensinado o conceito OO muito básico. Faz anos que estou tentando despoluir minha mente. Herança em todo lugar é explicando como Forma Polígono < Retângulo < Quadrado, que não corresponde aos problemas do dia a dia.
May 19th, 2008 at 3:12 pm
Ah se eu ganhasse um centavo toda vez que eu abrisse uma “classe” com essas coisas.
Melhor que isso é getStatusQueSaoFinalizados, que retorna um array de Status :)
PS: Tá com problema no CSS dos códigos?
May 19th, 2008 at 4:33 pm
> pedido.setStatus(Pedido.Status.FINALIZADO);
Você foi bem otimista nessa :) O que eu vejo mais é algo do tipo
pedido.setStatus(’F');
sendo que ‘F’ é a letra que marca o status de finalizado no banco. -sigh–
May 19th, 2008 at 10:25 pm
Mais uma vez um ótimo post. Tenho muitos “amigos” profissionais que juram ser totalmente oo porque usam JPA e get/set…
ah um último comentário.
“Bom, de quem é a culpa é assunto para outro post…”
Estou esperando o outro :)
May 19th, 2008 at 10:26 pm
Importantissimo alerta!
EU sempre que treino alguem, independente de qual linguagem, sempre alerto pra esta questao. Gosto muito de usar o termo entidade para distinguir de classe… Sempre defendo: “Pense sobre as entidades envolvidas e esqueca a porra do cohdigo por enquanto!!!” e eh incrivel como a maioria dos erros/problemas que se encontra sao consequencia das pessoas ignorarem isso, seja por desatencao, ignorancia ou burrisse mesmo!
May 19th, 2008 at 10:30 pm
Parabéns pelo post, mas o que você acha daquelas classes utilitárias que vemos em diversos projetos?
StringUtil
DateUtil
ProjetoUtil…
Então Java não é 100% OO, ou melhor não é utilizada de sua maneira mais OO na grande maioria dos casos?Porque todo código que vejo geralmente usa get/set dessa maneira..
May 19th, 2008 at 10:46 pm
@Everton
Concordo, exceto pela parte do “esquecer o código”. Em uma linguagem de alto nível como as citadas no artigo design é código ;)
@Antonio
Voce nao precisa de uma linguagem OO para programar OO assim como uma linguagem OO te garante boas práticas. As classes utilitárias podem ser sintomas de um problema maior ou pragmatismo, depende do contexto.
May 19th, 2008 at 11:21 pm
Gostei! Sempre tentei defender esse ponto de vista aqui onde trabalho, mas nunca consegui imaginar um problema ou algum exemplo que contextualiza-se a minha idéia!
Agora fica mais fácil! :P
ótimo artigo!
May 19th, 2008 at 11:38 pm
Shoes, não seria melhor encapsular a logica de decidir se o pedido esta finalizado ou não em outra classe? Acho isso porque um produto não sabe se ele esta finalizado ou não, isso é decidido por uma lógica do ser humando; e também dependendo do dominio um mesmo produto poderia estar finalizado ou não.
Segue um exemplo da minha ideia:
public void notificarMudancaEm(Pedido p) {
….
if(ebayLogic.isPedidoFinalizado(p)){
servicoEmail.enviamensagemConclusao(p);
}
}
May 20th, 2008 at 12:04 am
@Diego
Não. O objeto é responsável por seu estado. Dessa forma você está delegando esse controle para outra classe e quebrando encapsulamento.
Da forma apresentada pelo Shoes o ser humano ainda tem o poder de decisão para cancelar um pedido, bastaria chamar pedido.cancelar();
Agora quem determina o que ocorre quando um pedido é cancelado fica dentro de Pedido.
May 20th, 2008 at 1:33 am
@Danilo
acho que entendi a visão de voces, estão pensando no método isFinalizado como um que simplesmente devolve uma propriedade que por exemplo poderia ser chamada isFinalizado, não e?
eu tinha entendido que o método iria verificar algumas coisas, realizar umas lógicas do negócio e dependendo disso iria retornar true ou false.
foi mal pela confusão
May 20th, 2008 at 2:26 am
@Diego
O metódo isFinalizado() irá verificar o que for necessário para determinar se o pedido está finalizado. Pode ser que seja apenas o retorno de uma propriedade ou pode envolver lógica (do negócio) que determina este estado.
May 20th, 2008 at 5:26 am
Mas se for ter logica de negocios no meio, então você nao acha que deveria estar em outra classe?
Imaginemos que para decidir se o pedido esta finalizado se deva chamar um serviço web e ler uma tabela na base de dados, entre outras coisas similares. No dominio real, isso não e realizado por um pedido e sim por uma pessoa. O papel dessa pessoa não deveria ser reprezentado numa classe?
Me parece que o pedido não tem por que conhecer sobre o negocio nem sobre implementações, não concorda?
May 20th, 2008 at 6:43 am
Excelente post Shoes!
Este exemplo que tu apresentastes é um dos mais comuns encontrados na maioria dos sistemas na qual passei, mas comum que isso é só o de verificar o tipo do usuário logado na aplicação, mas no final das contas segue os mesmos exemplos problemáticos que tu citou!
Outra coisa interessante que você falou foi sobre o problema dos get/set’s nas classes, infelizmente nem todos os frameworks funcionam como o Hibernate, como por exemplo a maioria (se não todos?) os frameworks para interface com usuário! Sendo, não é só porque você é obrigado a ter get/set que você precisa usa-los!
Enfim, parabéns pelo post, muito bom mesmo.
Abraços!
May 20th, 2008 at 7:05 am
Muito lembrado, Shoes!
O caso do “Status” é um caso típico!
Temos que nos policiar para não colocar c.setStatus(Status.ATIVO) quando o correto seria ter um métood c.ativar() com toda a lógica de negócios necessária para ativar o objeto.
Não é raro encontrar sistemas com “máquinas de estado” implementadas num esquema 100% procedural.
May 20th, 2008 at 10:12 am
Mais um ótimo post Shoes! Aprendendo muito cada vez que leio seu blog.
@Diego
Se eu entendi alguma coisa, um pedido define seu estado pelos seus próprios atributos(incluindo agregações), se estes atributos são preenchidos do banco de dados ou de web services é responsabilidade da camada que lhe retorna este objeto “preenchido”(dao?).
No modelo/negócio, o isFinalizado() verifica seus atributos e checa se esta tudo OK. Esta é a lógica do negócio que este modelo(Pedido) representa, veja que este não cuida de buscar os dados, apenas de trabalha-los.
Bom… é assim que eu entendi.
May 21st, 2008 at 7:27 am
Então Diogo, desde o meu ponto de vista, uma classe como Pedido somente deveria ter getters e setters. Como o Shoes diz, uma classe representa estado + comportamento, so que um pedido não tem comportamento. O pedido não se mexe, não cresce, não fala, não pensa, etc.
Na vida real você não pergunta para o pedido se ele é válido ou não. Na vida real você pergunta para uma pessoa com o conhecimento do dominio se tal pedido é válido ou não. Sendo aquela afirmação verdadeira, por que no sistema teria que ser diferente? Não é o objetivo do DDD representar o dominio na vida real no desenho do software?
May 21st, 2008 at 9:10 am
Diego,
Antes de mais nada este tópico não é sobre Domain-Driven Design, mas ainda que fosse você precisa identificar o que é domínio e o que é processo atual. O objetivo de DDD ou OO não é modelar o mundo exatamente como ele é e sim modelar o domínio, o que é algo bem diferente.
No domínio todas as inormações que dizem se o pedido foi cancelado ou não estão dentro dele mesmo. O pedido não pensa e nem a pessoa neste caso. O pedido contêm a informação necessária, a pessoa apenas olha o pedido e te dá a informação dele.
A maioria dos papéis “burros” como olhar para uma folha de papel e ver se tem um carimbo REJEITADO não é modelada diretamente, apenas as tomadas de decisão. O papel da “pessoa que te diz isso” não é exercido por uma classe que extrai essa informação e sim por um intermediário como uma interface gráfica que exibe o status do pedido.
Ainda assim, mesmo que você resolva modelar o papel das “pessoas” no processo como classes (fugindo de DDD) isso não te impede de usar objetos que recebem mensagens.
May 21st, 2008 at 10:19 pm
Acho que entendi Shoes, obrigado pela explicação, ao parecer eu estava modelando de mais.
De qualquer modo se um dia desses você fizer um post mais detalhado sobre esse assunto acho que seria muito legal.
Ate mais.
May 23rd, 2008 at 9:40 am
[…] Objetos = estado + comportamentos Postado no 22, Maio, 2008 de samuelvalerio OO de verdade! […]
May 25th, 2008 at 3:06 am
[…] que boa parte das dúvidas quanto ao meu post sobre como objetos não possuem atributos se deve ao fato das pessoas não terem geralmente um conhecimento real sobre o que é troca de […]
May 28th, 2008 at 11:15 pm
Shoes, concordo que os objetos precisam ter dados + comportamento. Mas fico um pouco confuso na hora de projetar sistemas distribuídos. Como fica esta arquitetura em um sistema distribuído, com EJB’s, com um framework como o JBoss Seam, onde o objeto praticamente só tem dados e o comportamento e as regras de negócio ficam no Session Bean, como um Business Object?
May 28th, 2008 at 11:24 pm
@Anderson
Orientação a Objetos em si não fala de sistemas distribuídos, ou persistência, ou segurança, ou qualquer coisa parecida. Dado isso você tem que pensar que não pode expôr objetos, tem que expôr componentes.
EJBs, em chamadas remotas, são componentes e não objetos.
June 16th, 2008 at 1:37 pm
[…] Não devemos pensar em uma classe como uma mera coleção de atributos. Objetos também não são atributos mais funções, e sim estado + comportamento. O Phillip Calçado explica muito bem isso! Refatorando a linha 2 […]
June 29th, 2008 at 10:30 pm
[…] post Objetos não são atributos + funções o autor apresenta de uma forma bastante humorada os grandes problemas do ensino da Orientação a […]