“Use o mapa em vez da classe para representar dados” - Rich Hickey


19

Em este vídeo por Rich Hickey , o criador Clojure, ele aconselha a usar o mapa para representar dados em vez de usar uma classe para representá-lo, como foi feito em Java. Não entendo como pode ser melhor, pois como o usuário da API pode saber quais são as chaves de entrada se elas são simplesmente representadas como mapas.

Exemplo :

PersonAPI {
    Person addPerson(Person obj);
    Map<String, Object> addPerson(Map<String, Object> personMap);
}

Na segunda função, como o usuário da API pode saber quais são as entradas para criar uma pessoa?



Eu também gostaria de saber isso e acho que a pergunta de exemplo não responde muito bem.
629 sydan

Eu sei que já vi essa discussão antes em algum lugar no SE. Eu acredito que era no contexto do JavaScript, mas os argumentos eram os mesmos. Não consigo encontrá-lo.
Sebastian Redl

2
Bem, como Clojure é um Lisp, você deve fazer as coisas apropriadas para o Lisp. quando você usa Java, codifique em ... bem, Java.
AK_

Respostas:


12

Resumo executivo (TM)

Você recebe algumas coisas.

  • Herança prototípica e clonagem
  • Adição dinâmica de novas propriedades
  • Coexistência de objetos de diferentes versões (níveis de especificação) da mesma classe.
    • Objetos pertencentes às versões mais recentes (níveis de especificação) terão propriedades "opcionais" extras.
  • Introspecção de propriedades, antigas e novas
  • Introspecção de regras de validação (discutidas abaixo)

Há uma desvantagem fatal.

  • O compilador não verifica se há strings incorretas.
  • As ferramentas de refatoração automática não renomearão os nomes das chaves de propriedade para você - a menos que você pague pelos nomes sofisticados.

O problema é que você pode obter introspecção usando, hum, introspecção. Isto é o que geralmente acontece:

  • Ativar reflexão.
  • Adicione uma grande biblioteca de introspecção ao seu projeto.
  • Marque vários métodos e propriedades de objetos com atributos ou anotações.
  • Deixe a biblioteca de introspecção fazer a mágica.

Em outras palavras, se você nunca precisar interagir com o FP, não precisará seguir o conselho de Rich Hickey.

Por último, mas não menos importante (nem o mais bonito), embora usar Stringcomo chave de propriedade faça o sentido mais direto, você não precisa usar Strings. Muitos sistemas legados, incluindo o Android ™, usam IDs inteiros extensivamente em toda a estrutura para se referir a classes, propriedades, recursos etc.

Android é uma marca comercial da Google Inc.


Você também pode fazer os dois mundos felizes.

Para o mundo Java, implemente os getters e setters como de costume.

Para o mundo do FP, implemente o

  • Object getPropertyByName(String name)
  • void setPropertyByName(String name, Object value) throws IllegalPropertyChangeException
  • List<String> getPropertyNames()
  • Class<?> getPropertyValueClass(String name)

Dentro dessas funções, sim, código feio, mas existem plugins IDE que o preencherão, usando ... uh, um plug-in inteligente que seu código.

O lado Java das coisas terá o mesmo desempenho de sempre. Eles nunca usarão essa parte feia do código. Você pode até querer escondê-lo do Javadoc.

O lado FP do mundo pode escrever o código "leet" que eles querem, e geralmente não gritam com você sobre o código ser lento.


Em geral, o uso de um mapa (bolsa de propriedades) no lugar do objeto é comum no desenvolvimento de software. Não é exclusivo da programação funcional ou de qualquer tipo específico de linguagem. Pode não ser uma abordagem idiomática para qualquer idioma, mas há situações que exigem isso.

Em particular, serialização / desserialização geralmente requer uma técnica semelhante.

Apenas algumas idéias gerais sobre "mapa como objeto".

  1. Você ainda precisa fornecer uma função para validar um "mapa como objeto". A diferença é que "mapear como objeto" permite critérios de validação mais flexíveis (menos restritivos).
  2. Você pode adicionar facilmente campos adicionais ao "mapa como objeto".
  3. Para fornecer uma especificação do requisito mínimo de um objeto válido, você precisará:
    • Listar o conjunto de chaves "minimamente necessário" esperado no mapa
    • Para cada chave cujo valor precisa ser validado, forneça uma função de validação de valor
    • Se houver regras de validação que precisem verificar vários valores de chave, forneça isso também.
    • Qual o benefício? Fornecer a especificação dessa maneira é introspectivo: você pode escrever um programa para consultar o conjunto de chaves minimamente necessário e obter a função de validação para cada chave.
    • No POO, tudo isso é acumulado em uma caixa preta, em nome de "encapsulamento". No lugar da lógica de validação legível por máquina, o chamador só pode ler a "documentação API" legível por humanos (se felizmente existir).

commonplaceparece um pouco forte para mim. Quero dizer, é usado como você descreve, mas também é uma daquelas coisas notoriamente desleixadas / frágeis (como matrizes de bytes ou ponteiros), que as bibliotecas tentam esconder.
Telastyn

@Telastyn Essa "cabeça feia de mil cobras" geralmente ocorre no limite de comunicação entre dois sistemas, onde, por algum motivo, o canal de comunicação ou entre processos não permite que os objetos sejam teleportados intactos. Eu acho que novas técnicas, como Protocol Buffers, quase eliminaram esse caso de uso arcaico do mapa como objeto. Ainda pode haver outros casos de uso válidos, mas tenho pouco conhecimento disso.
Rwong 6/02

2
Quanto às desvantagens fatais, concorde. Porém, se os nomes das chaves de propriedade "fácil de digitar incorretamente" e "difícil de refatorar" forem mantidos, tanto quanto possível, em constantes ou enumerações , esse problema desaparecerá. Claro, isso limita a extensibilidade:
User949300 7/15/15

Se a "única desvantagem fatal" é realmente fatal, por que algumas pessoas são capazes de usá-la efetivamente? Além disso, classes e tipagem estática são ortogonais - você pode definir classes no Clojure, mesmo que seja digitado dinamicamente.
19419 Nathan Davis

@NathanDavis (1) Admito que minha resposta foi escrita de uma perspectiva de digitação estática (C #) e escrevi essa resposta porque compartilho o mesmo ponto de vista do solicitante. Admito que não tenho um ponto de vista centralizado em FP. (2) Bem-vindo ao SE.SE, e como você é uma figura respeitada em Clojure, reserve um tempo para escrever sua própria resposta se as existentes não forem satisfatórias. Os votos negativos subtraem a reputação e as novas respostas atraem votos positivos, o que aumenta a reputação rapidamente. (3) Posso ver como "objetos incompletos" podem ser úteis - você pode consultar duas propriedades para um determinado objeto (nome, avatar) e deixar de fora o resto.
rwong

9

É uma excelente conversa de alguém que realmente sabe do que está falando. Eu recomendo que os leitores assistam a coisa toda. São apenas 36 minutos.

Um de seus principais pontos é que a simplicidade abre oportunidades para mudanças mais tarde. A escolha de uma classe para representar a Personfornece o benefício imediato da criação de uma API estaticamente verificável, como você apontou, mas que vem com o custo de limitar oportunidades ou aumentar os custos de mudança e reutilização posteriormente.

Seu argumento é que o uso da classe pode ser uma escolha razoável, mas deve ser uma escolha consciente que tenha plena consciência de seu custo, e os programadores tradicionalmente fazem um trabalho muito ruim ao perceber esses custos, muito menos levá-los em consideração. Essa escolha deve ser reavaliada à medida que seus requisitos aumentam.

A seguir, algumas alterações de código (uma ou duas mencionadas na conversa) que são potencialmente mais simples usando uma lista de mapas em comparação com o uso de uma lista de Personobjetos:

  • Enviando uma pessoa para um servidor REST. (Uma função criada para colocar uma Mapdas primitivas em um formato transmissível é altamente reutilizável e pode até ser fornecida em uma biblioteca. PersonÉ provável que um objeto precise de código personalizado para realizar o mesmo trabalho).
  • Construa automaticamente uma lista de pessoas a partir de uma consulta de banco de dados relacional. (Novamente, uma função genérica e altamente reutilizável).
  • Gere automaticamente um formulário para exibir e editar uma pessoa.
  • Use funções comuns para trabalhar com dados pessoais altamente não homogêneos, como um aluno versus um funcionário.
  • Obtenha uma lista de todas as pessoas que residem em um determinado código postal.
  • Reutilize esse código para obter uma lista de todas as empresas em um determinado CEP.
  • Adicione um campo específico do cliente a uma pessoa sem afetar outros clientes.

Resolvemos esses tipos de problemas o tempo todo e temos padrões e ferramentas para eles, mas raramente paramos para pensar se escolher uma representação de dados mais simples e flexível no começo facilitaria nosso trabalho.


Existe um nome para isso? Digamos, Mapeamento de Propriedade de Objeto ou Mapeamento de Atributo de Objeto (na mesma linha do ORM)?
Rwong 6/02

4
Choosing a class to represent a Person provides the immediate benefit of creating a statically-verifiable API... but that comes with the cost of limiting opportunities or increasing costs for change and reuse later on.Errado e incrivelmente falso. Isso melhora a sua oportunidade de alterar posteriormente, porque quando você faz uma alteração de última hora, o compilador encontrará e apontará automaticamente todos os lugares que precisam ser atualizados para acelerar toda a sua base de código. É no código dinâmico, onde você não pode fazer isso, que realmente se acopla às escolhas anteriores!
Mason Wheeler

4
@MasonWheeler: O que você realmente está dizendo é que valoriza a segurança do tipo tempo de compilação em detrimento das estruturas de dados mais dinâmicas (e com menor flexibilidade).
Robert Harvey

1
Polimorfismo não é um conceito restrito à POO. No caso de mapas, você pode ter polimorfismo inclusivo (se os elementos forem subtipos de algum tipo que o mapa possa manipular) ou polimorfismo ad-hoc (se os elementos estiverem marcados como uniões). Isso é interno. As operações que podem ser executadas em um mapa também podem ser polimórficas. Polimorfismo paramétrico quando usamos a função de ordem superior em elementos ou ad-hoc ao despachar. O encapsulamento pode ser alcançado com namespaces ou outras formas de gerenciamento de visibilidade. Fundamentalmente, o isolamento de objetos não é igual a atribuir operações a tipos de dados.
siefca

1
@GillBates, por que você diz isso? Você perde a oportunidade de colocar esses métodos virtuais "dentro do mapa" - mas é exatamente disso que Rich Hickey fala: "ActiveObjects" são realmente um antipadrão. Você deve tratar os dados como são (dados) e não os entrelaçar com o comportamento. Existem enormes benefícios de simplicidade a serem alcançados ao separar as preocupações.
Virgil

4
  • Se os dados tiverem pouco ou nenhum comportamento, com conteúdo flexível que provavelmente será alterado, use um Mapa. O IMO, um "javabean" típico ou "Objeto de Dados" que consiste em um Modelo de Domínio Anêmico com N campos, N setters e N getters, é uma perda de tempo. Não tente impressionar os outros com sua estrutura glorificada, envolvendo-a em uma classe smancia sofisticada. Seja honesto, esclareça suas intenções e use um mapa. (Ou, se fizer sentido para o seu domínio, um objeto JSON ou XML)

  • Se os dados tiverem um comportamento real significativo, também conhecido como métodos ( Tell, Don't Ask ), use uma classe. E dê um tapinha nas costas por usar programação real orientada a objetos :-).

  • Se os dados tiverem muitos comportamentos essenciais de validação e campos obrigatórios, use uma classe.

  • Se os dados têm uma quantidade moderada de comportamento de validação, isso é limítrofe.

  • Se os dados disparam eventos de mudança de propriedade, isso é realmente mais fácil e muito menos tedioso com um Mapa. Basta escrever uma pequena subclasse.

  • Uma desvantagem principal do uso de um mapa é que o usuário deve converter os valores em Strings, ints, Foos, etc. Se isso for altamente irritante e propenso a erros, considere uma classe. Ou considere uma classe auxiliar que agrupe o mapa com os getters relevantes.


1
Na verdade, o que Rich Hickey argumenta é que, se os dados têm um comportamento real significativo ... você provavelmente está fazendo tudo errado no "design". Dados são "informações". As informações no mundo real NÃO são "um local onde os dados são armazenados". As informações não possuem "operações que controlam como as informações são alteradas". Não transmitimos informações informando às pessoas onde elas estão armazenadas. As metáforas orientadas a objetos são algumas vezes um modelo apropriado do mundo ... mas, na maioria das vezes, elas não são. É o que ele diz - "pense no seu problema". Nem tudo é um objeto - poucas coisas são.
Virgil

0

A API para a maptem dois níveis.

  1. A API para mapas.
  2. As convenções do aplicativo.

A API pode ser descrita no mapa por convenção. Por exemplo, o par :api api-validatepode ser colocado no mapa ou :api-foo validate-foopode ser a convenção. O mapa pode até armazenar api api-documentation-link.

O uso de convenções permite que o programador crie uma linguagem específica de domínio que padronize o acesso entre os "tipos" implementados como mapas. O uso (keys map)permite determinar propriedades no tempo de execução.

Não há nada de mágico nos mapas e não há nada de mágico nos objetos. É tudo expedição.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.