Quando uma pessoa aprende a programar em Java, especialmente se ela já programava em outra linguagem antes, ela não olha a linguagem java como uma forma de escrever descrições de objetos mas apenas como um conjunto de “comandos” que estão sendo dados. Isto é uma pena. Não só é uma pena, mas a razão de muitos erros de estruturação de codigo em java. A primeira coisa necessária para programar em Java é conhecer Programação Orientada a Objetos, pois apenas assim faz sentido aquilo que o java oferece.
O primeiro erro é confundir objeto com estrutura de dados. Em algumas linguagens não orientadas a objetos é possivel trabalhar com tipos que compoem diferentes variáveis numa variável só agrupando dados de forma mais concisa. Estas estuturas auxiliam na passagem de parametros, e até na tipagem do programa, mas não são objetos.Contudo, é comum não conseguir diferenciá-los porque ambos têm atributos.
Objectos não são apenas formados por atributos, eles são também formados por comportamento. Este comportamento pode, ou não, depender do valor dos atributos ou operar sobre eles. Claro que é possivel também conceber objetos que apenas detêm comportamento sem nenhum atributo ou propriedade. Mas não é isto que diferencia uma estrutura de dados de um objeto.
Um objeto se diferencia de uma estrutura de dados porque têm uma responsabilidade. A responsabilidade advém do papel que o objeto representa no sistema, e esse papel advém do significado que o objeto têm no sistema. A responsabilidade do objeto leva a uma classificação do objeto por papeis no sistema e a existência de vários objetos que se auxiliam para formar o sistema induz a uma classificação hierarquizada. Portanto, uma outra forma de dizer que objetos têm responsabilidade é dizer que objetos pertencem a uma hierarquia de classificações.A existencia de hierarquia de classificação está intimiamente ligada à operação de herança ( que seria impossivel existir sem dita hierarquia). Estruturas de dados, não pertencem a uma hierarquia, e portanto, embora fisicamente semelhantes, estruturas de dados não são objetos.
Mas podem objetos ser estruturas de dados ? A resposta rápida é : sim. A resposta longa é : podem, mas apenas se essa for a sua responsabilidade.
Objetos não se podem eximir de possuir responsabilidade e é um desafio manter apenas uma responsabilidade por objeto. Mesmo quando o desenvolvedor é inexperiente todo o objeto usado ganha uma responsabilidade no sistema mesmo quando o desenvolvedor não é consciente disso e atribuir a responsabilidade correta é que é o real desafio da programação orientada a objetos.
A programação orientada a objetos advém dos principios de modelagem orientada a objetos que é uma atividade abstrata em si mesmo e independente de qualquer tecnologia ou linguagem de programação. Modelar o objeto corretamente passa por definir seus atributos e métodos, mas principalmente por definir sua responsabilidade.
Se um objeto pode ser uma estrutura de dados, isso significaria que a responsabilidade do objeto é manter referencia a dados. Isso nos leva a duas prespetivas. A primeira é a do objeto que é apenas um cabide de dados e permite transportar os dados de forma agrupada de um ponto do sistema para o outro, sendo o objeto totalmente neutro em relação à razão dos dados estarem agrupados daquela forma e terem aqueles valores. Esta seria a melhor aproximação a uma estrutura de dados convencional, não orientada a objetos. Por outro lado, podemos pensar no objeto que atua como protetor dos dados que guarda, mantendo coerencia nos valores e com pelo conhecimento de porquê aqueles dados estão agrupados.O primeiros são meros objetos de transporte (Transport Objects, TO) e os segundos são objetos de valor (Value Objects, VO). É clara a diferença entre estes dois tipos. Poderiamos argumentar que o objeto de valor que agrupa os valores por uma razão e com conhecimento dessa razão, acaba servindo, também, para transportar esses dados de um ponto do sistema para outro. Aqui jaz a diferenciação: os dados que o TO transporta podem ser utilizados em diferentes pontos do sistema, como o sistema bem entender. Todos os dados são publicos e a sua manipulação é livre. Os dados que o VO contém não podem ser utilizados desassociadamente ou condicionalmente conforme os interesses do sistema. O sistema está obrigado a seguir as regras que o próprio VO impõe.
Em modelagem orientada a objetos aprendemos que os conceitos do mundo real devem ser incorporados no sistema de uma forma que a cooperação lógica entre os objetos seja equivalente à cooperação dos conceitos ( ou dos objetos reais traduzidos por eles) no mundo real. É por isto que modelamos classes Cliente, Pedido e Produto e as identificamos com os conceitos de mesmo nome no mundo real e ao dizermos que “O cliente faz um pedido composto de produtos” não existe diferença se essas palavras se referem a objetos no sistema ou às entidades no mundo real. Para que este mecanismo funcione, é claro que precisamos conseguir converter os conceitos reais em objetos. A este processo que usamos para criar equivalencia entre objetos e conceitos reais dá-se o nome de: Abstração.
A abstração dos conceitos reais é a fonte da origem dos objetos. Abstração não significa neste contexto fazer as coisas mais dificeis, mais universais ou mais matemáticas, mas apenas e só significa mapear os conceitos reais com os objetos que temos no modelo/sistema. Na realidade não é correto dizer que o mapeamento é entre os objetos e os conceitos, e sim que é entre os conceitos e as classes de objetos do modelo/sistema. O nome “classe” não é aleatório. Nos remete ao conceito de que todos os objetos pertecem a uma hierarquia de classificação, e portanto pertencem a uma ou a mais categorias (classes). Portanto, modelar de forma orientada a objetos é a realidade nada mais que criar uma (grande) arvore de categorias, ou melhor, de classes.
As classes de objetos que identificamos com conceitos do mundo real podem pertencer a 3 grandes classes de objetos : Entidades, Serviços e Valores. As classes de serviços são caracterizadas por não deter atributos ou propriedades. Todos os dados necessários à sua operação vêm do seu exterior. Entidades são caracterizadas como objetos de transporte(TO) de Valores para certas propriedades espeficicas que são,também, mapeadas do mundo real. Por exemplo, o cliente terá uma propriedade nome cujo valor será um conjunto de caracteres que, numa certa lingua, compoem o nome do cliente. Entidades podem também ter métodos que trabalham sobre as suas propriedades e/ou cujo resultado é alterado pelos valores dessas propriedades. Valores são objetos de valor (VO) que detém a informação sobre os valores , passo o plenoasmo, das caracteristicas das propriedades.
Existe atualmente uma confusão sobre o conceito de objeto como composição de dados e comportamento na forma de que certas pessoas confundem o fato dos objetos poderem ter dados e comportamento, com a obrigatoriedade de todos os objetos deverem ter dados e comportamento. Isto é pura e simples sandice. O que o objeto tem que respeitar é a responsabilidade ( ou responsabilidades) inerentes à(s) classe(s) a que pertence. Por este motivo, objetos da classe de serviço não detém atributos e isso é perfeitamente normal.
No mundo real, os conceitos são muito mais maleáveis que no mundo do software. Nem sempre é trivial destinguir entre o que é um objeto da classe de entidade, e o que é um objeto da classe de valores. Os exemplos classicos são o número de telefone e o endereço. Para um sistema comercial de compras e vendas o numero de telefone ou o endereço não passa de um valor de uma propriedade de alguma entidade como casa, pessoa ou cliente. Mas para o sistema dos correios o endereço é uma entidade ela mesma com propriedades e relacionamentos com outras entidades num vasto sistema de classificação completamente diferente daquele do sistema comercial. O mesmo poderiamos dizer do numero de telefone e da companhia de telefones. Portanto, se o conceito é melhor modelado como um objeto da classe de entidades ou da classe de valores não é constante entre sistemas de tipos diferentes.
Todas as linguagens orientadas a objetos oferecem em maior ou menor numero um conjunto de objetos de valor pré-prontos que podem ser usados como objetos da classes de Valores facilmente em qualquer entidade ou sistema. Estes objetos nunca serão usados como sendo da classe de entidade ou serviço, e portanto é seguro para a linguagem/plataforma de programação assumir algumas caracteristicas para estes objetos e de certa forma prover otimizações para eles. Em java estes tipos primários (não confundir com os tipos primitivos do java) são representados, por exemplo, pelas classes: String, Date, Boolean, Integer, Double e BigDecimal.
Porque estes tipos estão sempre disponiveis e são conhecidos de todos os desenvolvedores, é comum que eles sejam usados para os valores das propriedades de objetos de transporte e/ou de valor. O problema aqui é que quando queremos modelar o numero de telefone, por exemplo, como sendo um objeto pertencente na classe de objetos de valor, decidimos escolher um dos tipos primários em vez de criar um novo tipo ( classe) para ele. Então em vez da propriedade “telefone” do cliente ser um objeto do tipo “NumeroTelefone” é apenas um objeto String ou Integer. O problema com esta abordagem é a falha do processo de abstração. O erro é tão grande e grosseiro como se a pessoa aceitasse modelar o numero de telefone usando um Double( afinal todos os Integer cabem num Double e o Double permite ter uma parte decimal que poderia ser usada para o ramal … não tente isto em casa).
Para a grande maioria dos desenvolvedores parece um preço muito elevado criar uma classe com uma duzia de linhas para representar um conceito de valor. O argumento é que isso aumentaria o numero de classes no projeto, como se existisse alguma regra dizendo que o numero de classes deve ser abaixo de algum numero mágico. Não entendo isto. Realmente não consigo entender como uma desculpa destas que advem de pura preguiça pode triunfar sobre um raciocinio lógico, cientifico, e comprovado da modelagem orientada a objetos. O que a modelagem orientada a objetos sugere é que o numero de abstrações seja o minimo possivel. Sendo que as palavras chave aqui são “possivel” e “abstração”. É o numero de mapeamentos entre o mundo real e mundo dos objetos que tem que ser minimizado. Isto porque esses mapeamentos são complexos e frageis ( como o exemplo do endereço e telefone demonstram) e portanto, ter mapeamentos demais apenas aumenta a complexidade e risco do modelo estar furando em algum ponto. Porque as pessoas pessoas entender “abstração==classe” então acham que minimizar o numero de classes estão respeitando este principio. O problema é que o principio da separação de responsabilidade é mais importante do que a sugestão que comanda o numero de abstrações. Ou seja, quando a pessoa mapeia o numero de telefone à classe NumeroTelefone está separando a responsabilidade de descrever um numero de telefone. Usar um objeto String, poupa uma classe, mas coloca sobre a classe String a responsabilidade de além de descrever texto, também descrever numeros de telefone. E convenhamos que isto é um pouco idiota, pois se eu quiser ter um método getRamal() no numero de telefone, isto seria trivial num objeto NumeroTelefone , mas impossivel num objeto String já que ele não é extensivel. Mas mesmo que String fosse extensivel, não o poderiamos extender e criar o nosso StringTelefone pois isso significaria aumentar uma classe no nosso projeto e seria vetado pelas mesma razão (idiota) que veta a criação de NumeroTelefone em primeiro lugar.
É importante seguir principios , sugestões e boas práticas.Eu acredito muito nisto. Mas veja, existe uma ordem em que estas coisas têm que ser seguidas e existem prioridades. O principio de separação de responsabilidade tem prioridade sobre todos as sugestões e boas práticas que alguem imaginar.
Isto nos leva ao conceito de que, embora aos olhos destreinados pareça pequeno o ganho de criar um novo tipo (classe) fazer isso é a única saida lógica num caso assim. Estes “pequenos tipos” ganharam um nome – Tiny Types – exactamente para deixar clara a sua importância. Mas entenda que a necessidade de batizar estes tipos de objetos apenas advém do desconhecimento e incompetência tecnica da maioria dos desenvolvedores que não sabe seguir os principios mais básicos da orientação a objetos. Eles esquecem que todos os conceitos reais presentes no sistema precisam ser abstraidos e que violar isto é criar buracos no modelo e por consequencia no código e no sistema.
Existem outras formas de dizer a mesma coisa e revelar a mesma importância da operação de abstração para a correta modelagem orientada a objetos. Uma que ganhou muita fama ultimamente é chamada de “Linguagem Ubiqua” (Ubiquos Languagem) introduzida pelo pessoal adepto da filosfia Domain Driven Development (também chamada Domain Driven Design). O conceito aqui é que os mesmos termos (conceitos) que as pessoas usam no mundo real para se referirem aos intrevenientes no processo do mundo real que o software irá mimetizar devem ser usados no codigo. Isto é exactamente o mesmo que dizer que os conceitos devem ser abstraidos. A diferença é que dizer “os conceitos devem ser abstraidos” é uma expressão tecnica, enquanto “os mesmos termos que as pessoas usam no mundo real para se referirem aos intrevenientes no processo do mundo real que o software” é uma expressão usada por pessoas comuns não-tecnicos. As pessoas que estudaram orientação a objetos e conhecem a operação de abstração e não a confundem com “tornar as coisas mais complexas” sempre souberam que é excelente ideia colocar os nomes/termos/conceitos reais no código … afinal foi por causa disso que se inventou o próprio conceito de Objeto e o paradigma de Orientação a Objetos : porque queriamos que os programas tivessesm a mesma expressividade que o mundo real. É simples desconhecimento achar que isto é algo novo, e é simples maldade se aproveitar do desconhecimento da maioria dos desenvolvedores – que teve uma fraca preparação académica – para passar a “linguagem ubiqua” como algo novo e inovador. No fundo não passa de uma ferramenta de marketing igual ao dos Tiny Types para fazer as pessoas aceitarem o que elas deveriam ter aprendido na escola desde o começo : abstração.
Toda e qualquer impedimento em orientação a objetos, seja modelagem, seja implementação, advém do conceito de objeto ( redundante, mas importante ter sempre isto presente). O conceito de objeto significa entender que existe uma separação de responsabilidade e que isso que define “objeto” e o destingue de “estrutura de dados”. Simultaneamente essa responsabilidade classifica o objeto em uma hierarquia e a hierarquia de responsabilidades implica em sermos capazes de classificar em qual lugar dessa hierarquia o objeto pertence. A operação que usamos para fazer isto é a : Abstração.
A correta abstração do problema que o sistema deve resolver leva à correta hierarquia de objetos para aquele sistema e nem sempre essa hierarquia é a mesma para sistemas diferentes. Nem sempre é claro se o conceito será mapeado como um objeto de entidade, serviço, ou um valor, um objeto de transporte ou um objeto de valor. Contudo, independentemente da classificação é mandatório que o conceito seja mapeado para um tipo, uma classe e esse é a razão por detrás dos conceitos de Tiny Type e da Linguagem Ubiqua.
O principio de separação de responsabilidade ( um dos 5 principios da orientação a objetos) tem prioridade sobre a sugestão de manter um conjunto minimo de abstrações, e por consequencia de classes e em nenhum caso o aumento do numero de classes pode ser usado como desculpa para não respeitar o principio de separação de responsabilidade.
Modele corretamente o seu sistema. Aprenda Orientação a Objetos e poderá fazer seus sistemas tranquilamente sem precisar de muletas. Não tenha medo de criar classes. Tenha apenas medo delas não representarem nada de util ou real no processo que o seu software está tentando desempenhar. Se todos seguirmos isto teremos menos buzz words para nos preocupar e poderemos tornar nossa atenção para o que realmente interessa.
Sergio, parabéns pelo artigo muito esclarecedor. Só uma dúvida sobre objetos de entidade e objetos de valor, teria como dar mais alguns exemplos e em qual contexto se encaixariam.
No texto eu tentei diferenciar objetos de valor e valores. Objetos de valor são objetos que conhecem e de certa forma protegem os atributos que contêm.
Valores são objetos de valor especias porque sua responsabilidade se resume a proteger atributos que no seu conjunto representam uma quantidade de alguma coisa, ou o valor de uma propriedade como Cor.
Outros exemplos de valores seriam : Integer, Double, Date, BigDecimal, String, todas as enum e implementações de padrões como Money e objetos como CPF, CNPJ, NumeroTelefone. Ou seja, todos os objetos que podem ser usados como valor de uma propriedade ou atributo.
Entidades são objetos de valor que embora sejam formados por valores são mapeamentos com coisas da vida real que interagem. As regras dessa interação é o que queremos transportar para o codigo e para o software. Entidades são objetos participantes em atividades do software e têm papeis bastante principais. Exemplos são Cliente, Fornecedor, Produto, Estoque, Loja, Pessoa, Automovel, Embarcação, Duplicata, Cheque, Boleto, Banco, Caixa, Impressora, etc.
Como tentei exemplificar com o endereço e o numero de telefone nem sempre um conceito do mundo real é obrigatoriamente um valor ou uma entidade, contudo, todos são Objetos de Valor e nunca meras estruturas de dados.
Sergio, muito obrigado pelos esclarecimentos que trouxeram mais valor ao conceito tratado. Gostaria mais uma vez de parabenizá-lo pelo artigo, venho tentando adquirir esses conceitos e outros a algum tento e seus artigos estão me ajudando muito. Fico agradecido por compartilhar o seu conhecimento.
Sergio, excelente post.
Já que falou sobre tiny types, gostaria de esclarecer uma dúvida.
Eu concordo plenamente quando vc fala sobre SoC. Concordo que (como no exemplo citado) não seja da classe String ou Integer a responsabilidade de “representar” um telefone. Entretanto, o que vc diria sobre o acoplamento que essa(s) classe(s) gerariam? não ficaria muito grande? Eu até vejo o Spring (ou qq outro framework de IoC) como uma solução para isso, mas ainda assim tenho minhas dúvidas de se conceitualmente estaria correto usá-lo nesse sentido.
Em relação ao DDD vc está coberto de razão. Eu comprei o livro do Evans há um ano atrás esperando algo de novo, mas na verdade é “mais do mesmo”. Muitos adeptos dizem que a tal da “linguagem ubiqua” é a grande novidade, mas como vc mesmo relatou, é apenas um nome dado a um conceito que deveria ser implicito aos desenvolvedores que entendem de oriientação a objeto.
Obrigado pelo post!
Não vejo como Tiny Types geram acoplamento. Quando você usa um Date para representar uma data isso não é acoplamento. É a correta abstração do conceito pelo tipo mais próximo que vc tem. Se vc criar uma data usando o JodaTime isso sim gera acoplamento, mas porque vc está usando biblioteca de terceiros, não porque está usando Tiny Type. O spring e o IoC em geral não têm nada que ver com isto. Você não usa spring para injetar datas ou nomes em objetos (bom, pelo menos espero que não). Tiny types são valores , são tipos especializados para representar valores. Tiny Types podem interferir com a sua biblioteca de ORM como o Hibernate, mas o Hibernate dá ferramentas para poder manipular estes objetos também de forma simples. Não existe realmente nenhuma razão tecnica ou conceitual para não usar tiny types.