"Utilice el mapa en lugar de la clase para representar los datos" -Rich Hickey

19

En este video de Rich Hickey , el creador de Clojure, aconseja utilizar el mapa para representar datos en lugar de usar una clase para representarlo, como se hace en Java. No entiendo cómo puede ser mejor, ya que cómo puede el usuario API saber cuáles son las claves de entrada si simplemente se representan como mapas.

Ejemplo :

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

En la segunda función, ¿cómo puede el usuario API saber cuáles son las entradas para crear una persona?

Emil
fuente
También me gustaría saber esto y siento que la pregunta de ejemplo no responde.
sydan
Yo que he visto antes en alguna parte de esta discusión en SE. Creo que fue en el contexto de JavaScript, pero los argumentos fueron los mismos. Aunque no puedo encontrarlo.
Sebastian Redl
2
Bueno, ya que Clojure es un Lisp, debes hacer las cosas apropiadas para Lisp. cuando usas Java, codifica en ... bueno, Java.
AK_

Respuestas:

12

Resumen Exagg'itive (TM)

Tienes algunas cosas.

  • Herencia de prototipos y clonación
  • Adición dinámica de nuevas propiedades.
  • Coexistencia de objetos de diferentes versiones (niveles de especificación) de la misma clase.
    • Los objetos que pertenecen a las versiones más recientes (niveles de especificación) tendrán propiedades "opcionales" adicionales.
  • Introspección de propiedades, antiguas y nuevas.
  • Introspección de las reglas de validación (discutidas a continuación)

Hay un inconveniente fatal.

  • El compilador no busca cadenas mal escritas para usted.
  • Las herramientas de refactorización automática no cambiarán el nombre de las claves de propiedad por usted, a menos que pague por las elegantes.

La cuestión es que puedes obtener introspección usando, um, introspección. Esto es lo que suele pasar:

  • Habilitar la reflexión.
  • Agregue una gran biblioteca de introspección a su proyecto.
  • Marque varios métodos de objetos y propiedades con atributos o anotaciones.
  • Deje que la biblioteca de introspección haga la magia.

En otras palabras, si nunca necesita interactuar con FP, no tiene que seguir el consejo de Rich Hickey.

Por último, pero no menos importante (ni el más bonito), aunque usar Stringcomo clave de propiedad tiene el sentido más directo, no tiene que usar Strings. Muchos sistemas heredados, incluido Android ™, utilizan ID de números enteros ampliamente en todo el marco para referirse a clases, propiedades, recursos, etc.

Android es una marca registrada de Google Inc.


También puedes hacer felices a ambos mundos.

Para el mundo Java, implemente los captadores y establecedores como de costumbre.

Para el mundo FP, implemente el

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

Dentro de estas funciones, sí, código feo, pero hay complementos IDE que lo llenarán por ti, usando ... uh, un complemento inteligente que lee tu código.

El lado de Java de las cosas será tan eficiente como de costumbre. Nunca usarán esa parte fea del código. Es posible que incluso desee ocultarlo de Javadoc.

El lado FP del mundo puede escribir el código "leet" que quieran, y generalmente no te gritan porque el código es lento.


En general, el uso de un mapa (bolsa de propiedades) en lugar del objeto es común en el desarrollo de software. No es exclusivo de la programación funcional ni de ningún tipo particular de lenguajes. Puede que no sea un enfoque idiomático para un idioma determinado, pero hay situaciones que lo requieren.

En particular, la serialización / deserialización a menudo requiere una técnica similar.

Solo algunas ideas generales sobre "mapa como objeto".

  1. Aún debe proporcionar una función para validar tal "mapa como objeto". La diferencia es que "mapear como objeto" permite criterios de validación más flexibles (menos restrictivos).
  2. Puede agregar fácilmente campos de adición al "mapa como objeto".
  3. Para proporcionar una especificación del requisito mínimo de un objeto válido, deberá:
    • Enumere el conjunto de claves "mínimo requerido" esperado en el mapa
    • Para cada clave cuyo valor necesita ser validado, proporcione una función de validación de valor
    • Si hay reglas de validación que necesitan verificar múltiples valores clave, proporcione eso también.
    • Cual es el beneficio? Proporcionar la especificación de esta manera es introspectivo: puede escribir un programa para consultar el conjunto de claves mínimamente requerido y obtener la función de validación para cada clave.
    • En OOP, todos estos se enrollan en una caja negra, en nombre de "encapsulación". En lugar de la lógica de validación legible por máquina, la persona que llama solo puede leer "documentación API" legible por humanos (si afortunadamente existe).
rwong
fuente
commonplaceme parece un poco fuerte Quiero decir, se usa como lo describe, pero también es una de esas cosas notoriamente frágiles / poco frágiles (como conjuntos de bytes o punteros vacíos) que las bibliotecas hacen todo lo posible por esconderse.
Telastyn
@Telastyn Esta "cabeza fea de mil serpientes" generalmente ocurre en el límite de comunicación entre dos sistemas, donde por alguna razón el canal de comunicación o entre procesos no permite que los objetos se teletransporten intactos. Supongo que las nuevas técnicas como Protocol Buffers casi eliminan este caso de uso arcaico del mapa como objeto. Todavía puede haber otros casos de uso válidos, pero tengo poco conocimiento de eso.
rwong
2
En cuanto a los inconvenientes fatales, de acuerdo. Pero, si los nombres de clave de propiedad "fácil de escribir mal" y "difícil de refactorizar" se mantienen, tanto como sea posible, en constantes o enumeraciones , ese problema desaparece. Por supuesto, limita la extensibilidad a algunos :-(.
user949300
Si el "inconveniente fatal" es realmente fatal, ¿por qué algunas personas pueden usarlo de manera efectiva? Además, las clases y la escritura estática son ortogonales: puede definir clases en Clojure, aunque esté escrita dinámicamente.
Nathan Davis
@NathanDavis (1) Admito que mi respuesta está escrita desde una perspectiva de escritura estática (C #) y escribí esta respuesta porque comparto el mismo punto de vista del autor de la pregunta. Admito que me falta un punto de vista centrado en FP. (2) Bienvenido a SE.SE, y dado que usted es una figura respetada en Clojure, tómese el tiempo para escribir su propia respuesta si las respuestas existentes no son satisfactorias. Los votos negativos restan reputación y las nuevas respuestas atraen votos positivos que suman reputación rápidamente. (3) Puedo ver cómo los "objetos incompletos" pueden ser útiles: puede consultar 2 propiedades para un objeto determinado (nombre, avatar) y omitir el resto.
rwong
9

Esa es una excelente charla de alguien que realmente sabe de lo que está hablando. Recomiendo a los lectores que lo vean todo. Solo dura 36 minutos.

Uno de sus puntos principales es que la simplicidad abre oportunidades para el cambio más adelante. La elección de una clase para representar a Personproporciona el beneficio inmediato de crear una API verificable estáticamente, como señaló, pero eso conlleva el costo de limitar las oportunidades o aumentar los costos de cambio y reutilización más adelante.

Su punto es que usar la clase podría ser una opción razonable, pero debería ser una elección consciente que viene con plena conciencia de su costo, y los programadores tradicionalmente hacen un trabajo muy pobre al notar esos costos, y mucho menos tomarlos en consideración. Esa elección debe reevaluarse a medida que crecen sus requisitos.

Los siguientes son algunos cambios en el código (uno o dos de los cuales se mencionaron en la charla) que son potencialmente más simples usando una lista de mapas en comparación con el uso de una lista de Personobjetos:

  • Enviar una persona a un servidor REST. (Una función creada para poner una Mapde las primitivas en un formato transmisible es altamente reutilizable e incluso puede proporcionarse en una biblioteca. PersonEs probable que un objeto necesite un código personalizado para realizar el mismo trabajo).
  • Construya automáticamente una lista de personas a partir de una consulta de base de datos relacional. (Nuevamente, una función genérica y altamente reutilizable).
  • Genere automáticamente un formulario para mostrar y editar una persona.
  • Use funciones comunes para trabajar con datos de personas que son altamente no homogéneos, como un estudiante versus un empleado.
  • Obtenga una lista de todas las personas que residen en un determinado código postal.
  • Reutilice ese código para obtener una lista de todas las empresas en un determinado código postal.
  • Agregue un campo específico del cliente a una persona sin afectar a otros clientes.

Solucionamos este tipo de problemas todo el tiempo y tenemos patrones y herramientas para ellos, pero rara vez nos detenemos a pensar si elegir una representación de datos más simple y flexible al principio hubiera facilitado nuestro trabajo.

Karl Bielefeldt
fuente
¿Hay un nombre para esto? Digamos, ¿Asignación de propiedad de objeto o Asignación de atributo de objeto (en la misma línea que ORM)?
rwong
44
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.Incorrecto e increíblemente falso. Se mejora su oportunidad para cambiar más adelante, ya que cuando se realiza un cambio de última hora, el compilador encontrar y señalar que para cada lugar que necesita ser actualizado para llevar toda su código base a la velocidad de forma automática. ¡Es en código dinámico, donde no puedes hacer eso, que realmente te unes a las elecciones anteriores!
Mason Wheeler
44
@MasonWheeler: Lo que realmente está diciendo es que valora la seguridad de tipo de tiempo de compilación sobre las estructuras de datos más dinámicas (y de tipo más flexible).
Robert Harvey
1
El polimorfismo no es un concepto restringido a OOP. En el caso de los mapas, puede tener polimorfismo inclusivo (si los elementos son subtipos de algún tipo que el mapa puede manejar) o polimorfismo ad-hoc (si los elementos son uniones etiquetadas). Esto es lo interno. Las operaciones que se pueden realizar en un mapa también pueden ser polimórficas. Polimorfismo paramétrico cuando utilizamos funciones de orden superior en elementos o ad-hoc al despachar. La encapsulación se puede lograr con espacios de nombres u otras formas de gestión de visibilidad. Básicamente, el aislamiento de objetos no equivale a asignar operaciones a tipos de datos.
siefca
1
@GillBates ¿por qué dices eso? Simplemente pierde la oportunidad de poner esos métodos virtuales "dentro del Mapa", pero eso es exactamente de lo que habla Rich Hickey, "ActiveObjects" son realmente un antipatrón. Debe tratar los datos como lo que son (datos) y no entrelazarlos con el comportamiento. Hay grandes beneficios de simplicidad que se logran separando las preocupaciones.
Virgil
4
  • Si los datos tienen poco o ningún comportamiento, con contenido flexible que puede cambiar, use un Mapa. OMI, un "javabean" u "objeto de datos" típico que consiste en un modelo de dominio anémico con N campos, N setters y N getters, es una pérdida de tiempo. No intentes impresionar a otros con tu estructura glorificada envolviéndola en una clase sofisticada y sofisticada. Sé honesto, deja en claro tus intenciones y usa un Mapa. (O, si tiene sentido para su dominio, un objeto JSON o XML)

  • Si los datos tienen un comportamiento real significativo, también conocido como métodos ( decir, no preguntar ), utilice una clase. Y date una palmada en la espalda por usar programación real orientada a objetos :-).

  • Si los datos tienen muchos comportamientos de validación esenciales y campos obligatorios, use una clase.

  • Si los datos tienen una cantidad moderada de comportamiento de validación, eso es límite.

  • Si los datos disparan eventos de cambio de propiedad, eso es realmente más fácil y mucho menos tedioso con un Mapa. Solo escribe una pequeña subclase.

  • Una desventaja principal del uso de un mapa es que el usuario tiene que convertir los valores en cadenas, ints, foos, etc. Si esto es muy molesto y propenso a errores, considere una clase. O considere una clase auxiliar que envuelva el Mapa con los captadores relevantes.

user949300
fuente
1
En realidad, lo que Rich Hickey argumenta es que si los datos tienen un comportamiento real significativo ... probablemente esté haciendo todo el "diseño" incorrecto. Los datos son "información". La información, en el mundo real NO es "un lugar donde se almacenan los datos". La información no tiene "operaciones que controlan cómo cambia la información". No transmitimos información al decirle a la gente dónde está almacenada. Las metáforas orientadas a objetos son A VECES un modelo apropiado del mundo ... pero la mayoría de las veces, no lo son. Eso es lo que dice: "piensa en tu problema". No todo es un objeto, pocas cosas lo son.
Virgil
0

La API para a maptiene dos niveles.

  1. La API para mapas.
  2. Las convenciones de la aplicación.

La API se puede describir en el mapa por convención. Por ejemplo, el par :api api-validatese puede colocar en el mapa o :api-foo validate-foopodría ser la convención. El mapa incluso puede almacenar api api-documentation-link.

El uso de convenciones permite al programador crear un lenguaje específico de dominio que estandariza el acceso a través de "tipos" implementados como mapas. El uso (keys map)permite determinar propiedades en tiempo de ejecución.

Los mapas no tienen nada de mágico y los objetos no tienen nada de mágico. Todo es despacho.

ben rudgers
fuente