Orientação a objeto – Como encontrar as abstrações corretas? – orientação-a-objetos modelagem independente-de-linguagem

Pergunta:


Na verdade a pergunta que estou querendo fazer é exatamente essa: Como identificar classes em um sistema orientado a objetos?. Porém gostaria de pedir duas complementações à resposta aceita dela.

  1. Ela parece descrever o chamado OOA&D (Object-Oriented Analysis and Design) ou Análise e Projeto Orientados a Objeto. Não sei se esta é uma solicitação adequada ao formato do SO, mas eu gostaria de pedir uma referência canônica a respeito do assunto, e aceitando material complementar ao canônico (se possível indicando por que eu deveria preferir um material em relação a outro). Se fizer diferença, tenho maior experiência com Java. Atualmente estou em dúvida entre as seguintes referências:

    • Applying UML and Patterns (Craig Larman) – livro-texto adotado pela minha universidade após eu me formar; parece não entrar muito em detalhes no que diz respeito a encontrar as abstrações corretas, mas em seu texto o autor recomenda o Responsibility-Driven Design que é descrito no livro abaixo;
    • Object Design: Roles, Responsibilities, and Collaborations (Rebecca Wirfs-Brock, Alan McKean) – a autora é referência em OO; o livro introduz o RDD, que parece ser preciso sobre como escolher as abstrações corretas na hora de modelar as classes;
    • Object-Oriented Software Construction (Bertrand Meyer) – o autor é referência em OO, mas a última edição não é muito recente;
    • Object-Oriented Analysis and Design with Applications (Grady Booch et al.) – interessante porque compara abordagens clássicas e modernas para Análise Orientada a Objeto e dedica o capítulo 4 a “encontrar as abstrações corretas”;
    • Head First Object-Oriented Analysis & Design (Brett D. McLaughlin et al) – nenhuma recomendação especial, só gosto do estilo da série Head First.
  2. Uma pergunta mais específica a respeito da modelagem das classes. Confesso que apesar de achar que “programava OO” há muitos anos, pelo fato de ter estudado minha disciplina de POO, aprendido minha cota de design patterns, lido sobre SOLID, conhecer coesão/acoplamento, favorecer composição a herança e tudo mais, na verdade tinha muita liberdade com minhas classes e não entendia realmente a filosofia original por trás da orientação a objeto. Se entendesse, saberia há tempos o motivo de fazerem sentido certas “rules of thumb” como Tell Don’t Ask (que em sua forma mais amena adverte contra o vazamento de lógica de negócio para fora do objeto por meio de getters desnecessários) ou the er-er Principle, ou ainda a postagem Dance You Imps do Robert Martin. Objetos vão além de juntar estado e comportamento à revelia da nossa intuição e experiência individual; eles seriam mais como “mini-máquinas” que fornecem uma API de comandos para seus clientes (seus métodos públicos, que via de regra devem ser mais de um; caso contrário provavelmente é uma procedure disfarçada de objeto), deixam transparecer um mínimo de estado (isso é encapsulamento; não tem nada a ver com simplesmente criar getters para variáveis privadas) e se autogerenciam. Você não cria classes à sua revelia; você acha as classes que são mais apropriadas, muitas vezes tendo que descartar as escolhas inicialmente mais óbvias. Para mim esses conceitos não são nada intuitivos e acredito que haja material a respeito de como projetar classes corretamente seja nos livros acima ou em algum outro lugar, por isso a pergunta-título: “Como encontrar as abstrações corretas?”, que deve levar em conta essa correta filosofia de orientação a objeto. Na verdade gostaria de quebrar essa pergunta em duas partes: uma voltada para o longo prazo, na qual as respostas indicam leitura sobre o assunto; e uma para o curto prazo, visto que estou iniciando a codificação de um projeto novo, com regras de negócio, arquitetura, modelagem de banco e microserviços individuais já especificados e desde que constatei essa deficiência estou me sentindo inseguro a respeito de como projetar as classes dentro desses microserviços. Visto que a filosofia correta não é intuitiva para tantos programadores (é o que dizem os entendidos do assunto, e o que atesta o desconhecimento amplo desse material canônico que o @bigown falou, e vide também a errônea analogia de que os objetos devem refletir precisamente o mundo real [1] [2]), e visto que criar o seu diagrama de classes por intuição em vez de achar o mais apropriado também é incorreto, encontrar as abstrações certas parece um bocado mais difícil do que antes.

Autor da pergunta Piovezan

Piovezan

Apesar de muita gente interpretar de maneira simplória o versinho “Orientação a Objetos ajuda a representar o mundo real”, o fato é que é exatamente esta idéia que vai te ajudar a encontrar as abstrações corretas.

Desmistificando a “representação do mundo real”

  • O mapa com os nomes de ruas e bairros colado na parede lá em casa representa a minha cidade real?

  • O molde em gesso que meu dentista fez representa a minha boca real?

  • A planta que o arquiteto fez do prédio onde eu trabalho representa o lugar real onde eu trabalho?

A resposta para todas essas perguntas é sim e não:

  • O mapa me ajuda a achar um endereço mas não me diz quais ruas possuem boas calçadas para eu poder ir a pé tranquilo.

  • O molde do dentista ajuda-o a resolver problemas da minha dentição mas não poderia ajudar outro médico a diagnosticar um problema na mucosa.

  • A planta arquitetônica do prédio onde eu trabalho ajuda numa reforma ou na redisposição dos móveis mas não fala nada sobre a refrigeração no escritório, o relacionamento entre as pessoas, os projetos em que trabalhamos, etc.

Ou seja:

Um modelo representa de fato um objeto do mundo real, segundo a perspectiva ou abstração que interessa para a solução de um problema em específico.

Nenhum tipo de modelo neste universo é capaz de representar um objeto com todas as suas características e funções. Nenhum modelo é capaz nem mesmo de representar apenas as características e funções mais importantes de um objeto!

Cada modelo precisa representar apenas as características e funções de um objeto que interessam na solução de um problema em específico. Para isso que serve a OOP e é com esta idéia em mente que você conseguirá encontrar as abstrações corretas.

A este conjunto mínimo, ridiculamente pequeno, de características e funções dos objetos reais que nos interessam ao desenhar uma solução, podemos chamar de domínio do problema; ou, para brevidade, podemos chamar apenas de domínio.

Já o desenho da solução, a tradução do domínio para objetos, podemos chamar de modelo, e a atividade de desenhar o modelo podemos chamar de modelagem.

Creio que o que escrevi até agora você já sabia, mas é bom alinhar. De modo que você sabe também que o próximo tópico, em vez de se chamar Como transformar o mundo real em um modelo de objetos, ele se chama:

Como modelar o domínio?

Orientação a Objetos não é o mais difícil.

Representar o mundo real (dentro do conceito já explicado aqui) usando orientação a objetos também não é.

O que é mais difícil é convencer o programador a esquecer a tecnologia por um momento e se concentrar no problema do mundo real que precisa ser resolvido.

Quando o tópico é “orientação a objetos”, ouve-se muito as pessoas falando em “herança”, “polimorfismo”, “interface”, “generics”, “ORM”…

Programadores não gastam um minuto a mais para entender mais a fundo um requisito de negócio, mas não se importam de gastar horas tentando mapear via ORM uma herança de entidades (desnecessária) para o banco de dados.

Acontece que são os requisitos de negócio, o domínio do problema, que vai entregar as informações que você precisa para encontrar as abstrações corretas.

A conversa cuidadosa sobre o domínio, com pessoas que entendam do domínio, vai revelar os nomes dos objetos, as delimitações destes objetos, seus atributos e funções; e vai revelar também as interações entre estes objetos e como eles devem ser coordenados para a solução do problema.

Muitos programadores impõem sobre si mesmos a crença limitadora de que o cliente não sabe o que quer. Trabalhar com esta premissa leva a softwares mal desenhados, excessivamente complexos, marcados pelo desperdício e que não resolvem bem o problema.

Envolver o cliente (ver “product owner” e “domain experts”) é o que vai ajudar a compreender o domínio e portanto a identificar as abstrações corretas.

Conhecimento e experiência

É claro que conhecimento técnico de orientação a objetos é indispensável, pois o domain expert não vai te ensinar a programar.

Conhecimento de design patterns (conhecimento formal ou só por experiência mesmo), também é fundamental para você tomar boas decisões sobre como coordenar o trabalho dos objetos.

Mas este tipo de conhecimento técnico não é o que vai determinar uma modelagem boa ou ruim; ele é apenas pré-requisito. O fator determinante é o conhecimento do domínio e, lógico, a aplicação deste conhecimento na modelagem.

Como material de estudo, se você já tem experiência com OOP e design patterns e tudo o mais, recomendo o Domain-Driven Design, do Eric Evans.

Eu indico este livro quando o programador já atingiu a maturidade de perceber que seu conhecimento técnico não está sendo suficiente para projetar e implementar com sucesso um grande software.

É sempre bom lembrar que toda a filosia contida neste livro só faz sentido em domínios complexos (que não é o caso por exemplo de pequenos aplicativos mobile, sites web mais cadastrais ou de consulta, ou mesmo sistemas grandes e distribuídos com milhões de usuários mas com baixa complexidade de negócio).

Vou arriscar uma resposta simplista à pergunta principal:

Como encontrar as abstrações corretas?

Abordagem top-down

Numa reflexão pessoal, cheguei à conclusão de que uma das formas de resolver problemas que é mais eficiente para mim consiste numa abordagem top-down, indo do conceito mais abstrato e genérico para o mais concreto e específico.

Por exemplo, vamos supor que o cliente quer um programa para controlar um robô que prepara sucos. Voltando ao princípio mais básico da computação (entrada, processamento e saída), eu tenho:

  • Entradas: frutas, água e açúcar (ingredientes)
  • Processamento: robô usa o liquitificador, podem haver diferentes tipos de “receitas”
  • Saída: bebida

Então, em alto nível, posso determinar a interface principal do sistema:

class Robo {
    Suco preparar(Ingredientes, Procedimento)
]

Basicamente, você pede ao Robô para fazer o suco, fornece os ingredientes e diz qual o procedimento, recebendo como resultado a vitamina.

Claro, é algo muito simples, mas aqui está a vantagem. Uma boa modelagem, na definição que estou usando, inclui dois elementos principais:

  1. O fato de muitos detalhes não estarem representados dão grande liberdade à forma como tudo será implementado. Os processos podem ser quebrados em subprocessos, objetos mais abstratos podem ser compostos com outros objetos mais simples, as interfaces ou classes principais podem ter versões especializadas, as interações entre os elementos não estão totalmente definidas e assim por diante.
  2. Ao mesmo tempo temos uma “interface pública” bem definida para os “clientes” dessa classe fazerem suas vitaminas.

Resumindo:

A abstração “correta” seria a que permite você implementar e especializar as funcionalidades existentes com liberdade sem mudar os níveis mais altos.

Considerações

Orientação a objetos vs. outros paradigmas

Em várias questões que tem surgido esses dias, tem sido levantada a questão de vantagens da POO contra algum outro paradigma.

De forma alguma devemos ver isso como uma competição. Os diferentes paradigmas podem e devem ser usados em conjunto.

A POO, com suas classes e atributos não substitui a programação “procedural” ou imperativa nem impede o uso de programação funcional, mas adiciona capacidades de encapsulamento e abstração que seriam mais difíceis de expressar nos demais paradigmas.

Nada impede, por exemplo, que um código “procedural” construa uma coleção de objetos e execute uma operação de map-reduce para obter uma consolidação dos dados da coleção.

Estruturas de dados vs abstração

Note que na minha “modelagem”, nem entrei na questão das estruturas de dados utilizadas, isto é, quais atributos representam um ingrediente, etc.

O trunfo da POO não é em organizar conjuntos de dados, nem (na minha opinião) em representar entidades da vida real, mas sim na abstração ou no que é possível especificar sem definir os detalhes.

Os atributos da vida real podem ser representados em estruturas de dados “sem vida” e processadas por funções ou rotinas independentes.

Por outro lado a elegância de uma boa modelagem de classes está, na minha opinião, não na modelagem em si, mas no quão fácil e intuitiva ela é para as outras pessoas que irão acessar, implementar e estender. Disto, nenhuma linguagem puramente procedural chegou perto até hoje.

Tornando o conhecimento concreto

Não irei recomendar uma literatura específica do assunto. Na verdade, não conheço e não acho que exista nada que seja definitivo. Qualquer texto ou livro vai abordar no máximo alguns aspectos do que é modelar o problema em objetos.

A única forma que conheço para fazer uma modelagem “correta” é:

  1. Ler tudo o que puder sobre o assunto
  2. Tentar aplicar isso num problema da melhor forma que você conseguir
  3. Fazer prova de uso na prática e recolher feedback
  4. Identificar os problemas e pesquisar em soluções para eles
  5. Refazer o modelo tentando resolver os itens encontrados no item #4 para ter uma modelagem “mais correta”
  6. Repetir tudo dezenas de vezes

Um processo criativo

Ao contrário do que está descrito na pergunta, eu não diria que alguém encontra a abstração correta, como se isso fosse algo que existe independente do programador.

Programar é um processo criativo. Um artista não encontra a forma correta para pintar ou modelar algo, ele o faz arbitrariamente dentro dos limites que ele se impõe e, é claro, dentro de sua própria capacidade.

Design orientado a objetos: contexto histórico e situação atual

No início dos anos 90 houve uma explosão de metodologias de desenvolvimento de aplicações orientadas a objetos competindo entre si, muitas com ferramentas CASE próprias, denominada “method wars”. Dentre os livros citados na pergunta, o Object-Oriented Analysis and Design with Applications descreve o método de Booch, e o Object Design é a versão atualizada do método RDD, proposto inicialmente no livro Designing Object-Oriented Software. Destacam-se também o OMT, de James Rumbaugh, e o OOSE, de Ivar Jacobson. 1 2

Booch, Rumbaugh e Jacobson (“the three amigos”) se juntaram com a intenção de desenvolver um método unificado, mas desistiram e unificaram somente as notações de seus métodos individuais, dando origem à UML (Unified Modeling Language).
3

Permanece portanto a questão de qual dos métodos utilizar, em paralelo com a crescente adoção de métodos/frameworks de desenvolvimento de software ágeis que não estabelecem uma abordagem de design específica. Os métodos dos anos 90 são considerados mais “planned design”, em oposição ao chamado “evolutionary/continuous/emerging design” que pode ser feito mais ad hoc durante o desenvolvimento. Martin Fowler sugere combinar as duas abordagens. 4

Se houver algo errado por favor me corrijam.

Pontos específicos

Vou terminar de responder a pergunta com o que aprendi até o momento após algumas verificações e corrigindo algumas impressões erradas que tinha quando a fiz.

  • Para um problema há muitas soluções razoáveis e algumas ótimas. Além disso, diferentes abordagens de design (RDD, DDD, etc.) podem até mesmo ser combinadas entre si de maneira benéfica. Então o @utluiz está certo, não existe um design correto e nem um material canônico, temos que ler o máximo que pudermos a respeito do assunto. Frequentemente porém, diz a Rebecca, você primeiro faz o código funcionar e depois se preocupa em tornar os objetos mais simples.

  • Sim, objetos são como mini-máquinas, mas não existe nenhum impedimento quanto a eles fornecerem um só comando. O princípio er-er na verdade não generaliza a ideia de que objetos terminados em -er são ruins, essa é uma interpretação errada do mesmo. Bertrand Meyer alerta para objetos com um comando só mas apenas no caso em que eles não têm sequer um construtor parametrizado (aí sim provavelmente se trata de um procedimento disfarçado de objeto).

  • O espírito do princípio Tell Don’t Ask (manter no mesmo lugar os dados e os comportamentos que os manipulam) é bom e válido para o caso geral segundo Fowler e Rebecca, mas há algumas situações em que é interessante separá-los (por exemplo, no padrão Specification do DDD).

  • Pessoalmente tenho o hábito quase automático de aplicar DRY assim que aparece o primeiro sinal de duplicação no código, criando funções ou novas hierarquias de classes. Isso é parte do meu problema ao dizer que crio classes à revelia, e tem o nome de generalização prematura. Toda decisão de design deve ser tomada com cuidado porque engessa o código e gera um efeito cascata em outras decisões de design. Há então momentos em que é melhor deixar a duplicidade acontecer por um tempo até se ter uma boa visão do código para que a decisão de generalizar seja mais acertada.

Bom, apesar de achar que nenhuma resposta seja adequada pra sua pergunta, vou arriscar ajudar voce no caminho que voce deseja trilhar.

A resposta direta pra sua pergunta “Como identificar classes em um sistema orientado a objetos?”, seria “o que fizer sentido no que diz respeito a separação de responsabilidades da sua aplicação, com foco em reuso buscando não duplicar código desnecessário”.

Em um contexto mais amplo, a identificação de classes em uma aplicação, apesar de algumas técnicas passadas por alguns autores possam te ajudar de diversas formas, no final das contas é realmente a sua experiência e visão sistêmica que vão bater o martelo.

Eu diria que identificar essas classes e a relação entre elas (herança, polimorfismo, atributos) é uma combinação de bom senso, conhecimento e principalmente, experiência.

Eu sempre digo que programar e projetar sistemas não é como “levantar muro”, é muito mais como “pintar um quadro”. Alguém pode até te ensinar como aplicar as cores, mas só você, usando seu bom senso, conhecimento e experiência pode decidir qual cor usar em que momento usar elas, e diria mais, só depois de aplicar as cores algumas vezes você vai pegando o jeito de quais são melhores de usar nos momentos mais adequados.

Quanto a sua dúvida em relação às referências, eu diria, leia todas. A primeira vai te dar uma visão legal de UML, o que é um passo interessante para entender melhor orientação a objetos e também uma forma interessante de aprender como identificar classes, já que o exercício de identificação de casos de uso é um passo importante nesse caminho. As outras referências são bem técnicas, e dou destaque mesmo para o Heads First, apesar de achar uma literatura técnica e massante (leitura, exercício, questõeszinhas… coisa pra quem busca certificação).

Ainda sim recomendo pra você o livro “The Pragmatic Programmer” por alguns motivos:

1- Não é um livro focado em linguagem e conhecimento de OO não tem ligação direta com a linguagem que voce usa.
2- É um livro que explica situações do dia a dia de um programador, apresenta cenários lúdicos e conceitos de “Broken Window”, “Bend or Break” e “Stone Soup”
3- Vejo como literatura obrigatória para qualquer programador que deseja conceber arquiteturas e acumular boas práticas como me parece ser o seu caso.

Quanto ao trecho final da sua pergunta, acredito que tenha respondido uma boa parte dela na introdução inicial e ao longo dessa extensa resposta.

Finalizo aqui deixando duas dicas preciosas pra você:

  1. Segurança sobre as decisões técnicas tomadas de forma correta, sendo elas OO ou não, vêm com vivência em tecnologia e milhares de decisões erradas tomadas. Tanto dos outros, quanto suas. Não tenha medo de errar, mas também não erre por não consultar as pessoas a sua volta.
  2. Os livros ajudam você a aplicar as cores e são essenciais nessa caminhada, por que eles ajudam voce a construir uma bagagem conceitual que o dia a dia não consegue. Conheco muitos programadores que fazem as coisas no automático e não sabem por que estão fazendo. Esses dias mesmo um dos caras aqui me aplicou um padrão Singleton e não sabia que era um design pattern, ele só sabia que resolvia o problema dele. Em contrapartida, só o dia a dia vai te dar o feeling de como aplicar cada um destes conceitos.

Abraços e Boa Sorte

Fonte

Related Posts:

Qual a diferença entre AppCompatActivity e Activity? – android android-activity
Pergunta: Qual a diferença da AppCompatActivity para Activity ? A partir de qual versão a AppCompatActivity foi adicionada ao Android? Autor da pergunta Luhhh A diferença reside ...
Como abreviar palavras em PHP? – php string
Pergunta: Possuo informações comuns como nome de pessoas e endereços, e preciso que elas contenham no máximo 30 caracteres sem cortar palavras. Exemplo: 'Avenida Natalino João Brescansin' ...
Qual é a finalidade de um parêntese vazio numa declaração Lambda? – c# expressões-lambda característica-linguagem
Pergunta: Criei um exemplo de uma declaração Lambda sem argumentos, entretanto, estou com duvidas referente a omissão do parêntese vazio () na declaração. Veja o exemplo: class ...
Boas práticas para URI em API RESTful – api rest restful
Pergunta: Estou com dúvida em relação às URIs de alguns recursos da api que estou desenvolvendo. Tenho os recursos projetos e atividades com relação 1-N, ...
Dúvidas sobre a integração do MySQL com Java – java mysql netbeans
Pergunta: Estou criando um sistema no NetBeans, utilizando a linguagem Java e o banco de dados MySQL. Escrevi o seguinte código para realizar a conexão ...
Qual é a finalidade da pasta Model do framework Inphinit? – php inphinit
Pergunta: No Inphinit micro-framework existe a pasta Model que fica dentro da pasta application, e nela é onde ficam as classes, mas eu estou muito ...
Uso do ‘@’ em variáveis – javascript typescript coffeescript
Pergunta: Vejo em algumas linguagens que compilam para javascript, como TypeScript e CoffeeScript, o uso do @ em variáveis, como também, casos em que o ...
Qual tamanho máximo um arquivo JSON pode ter? – json arquivo
Pergunta: Vou dar um exemplo para conseguir explicar minha duvida: Preciso recuperar informação de imagens vindas de uma API, esse banco de imagens me retorna JSON's ...
O que é Teste de Regressão? – terminologia engenharia-de-software testes
Pergunta: Na matéria de Teste de Software o professor abordou um termo chamado Teste de Regressão, isto dentro da disciplina de teste de software. Sendo ...
O que é um construtor da linguagem? – php característica-linguagem
Pergunta: Em PHP, já li e ouvi várias vezes a respeito dos Construtores da Linguagem. Os casos que sempre ouvi falar deles foi em casos ...
Função intrínseca para converter numérico para string – cobol
Pergunta: Estou a tentar saber se existe alguma função intrínseca do COBOL para converter um data numérico para string sem precisar usar a cláusula REDEFINES: ( ...
Porque usar implements? – java android
Pergunta: Qual a diferença entre usar btn.setOnClickListener(new OnClickListener() { e public class MainActivity extends Activity implements OnClickListener{ Estive fazendo um curso de Android e meu professor falou que ...
O que é XHTML e quando deve ser usado? – html xml xhtml
Pergunta: O que eu sei é que o XHTML precisa ser XML válido. Isso implica, por exemplo, que todas as tags precisam ser fechadas. Por ...
Uma placa aceleradora de vídeo pode melhorar o desempenho não-gráfico? [fechada] – desempenho
Pergunta: Para desenvolver em Ruby on Rails, eu utilizo aqui uma máquina virtual do VirtualBox com Ubuntu Server 14.04 sem interface gráfica instalada. Recentemente descobri uma ...
Concat() VS Union() – c# .net
Pergunta: Qual a diferença entre Concat() e Union() ? Quando usar Concat() e quando usar Union() ? Somente pode ser usado em list ? ...

Deixe uma resposta

O seu endereço de email não será publicado. Campos obrigatórios marcados com *