«Utilisez la carte au lieu de la classe pour représenter les données» -Rich Hickey

19

Dans cette vidéo de Rich Hickey , le créateur de Clojure, il conseille d'utiliser la carte pour représenter les données au lieu d'utiliser une classe pour les représenter, comme cela est fait en Java. Je ne comprends pas comment cela peut être mieux, car comment l'utilisateur de l'API peut-il savoir quelles sont les clés d'entrée si elles sont simplement représentées sous forme de cartes.

Exemple :

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

Dans la deuxième fonction, comment l'utilisateur de l'API peut-il savoir quelles sont les entrées pour créer une personne?

Emil
la source
J'aimerais aussi savoir cela et je pense que l'exemple de question n'y répond pas tout à fait.
sydan
Je sais que j'ai déjà vu cette discussion quelque part sur SE. Je pense que c'était dans le contexte de JavaScript, mais les arguments étaient les mêmes. Je ne le trouve pas cependant.
Sebastian Redl
2
Eh bien, puisque Clojure est un Lisp, vous devriez faire des choses appropriées à Lisp. quand vous utilisez Java, codez ... bien Java.
AK_

Réponses:

12

Résumé Exagg'itive (TM)

Vous obtenez quelques choses.

  • Héritage et clonage prototypique
  • Ajout dynamique de nouvelles propriétés
  • Coexistence d'objets de versions différentes (niveaux de spécification) de la même classe.
    • Les objets appartenant aux versions les plus récentes (niveaux de spécification) auront des propriétés "optionnelles" supplémentaires.
  • Introspection de propriétés, anciennes et nouvelles
  • Introspection des règles de validation (discutée ci-dessous)

Il y a un inconvénient fatal.

  • Le compilateur ne vérifie pas pour vous les chaînes mal orthographiées.
  • Les outils de refactorisation automatique ne renommeront pas les noms de clé de propriété pour vous - à moins que vous ne payiez pour ceux de fantaisie.

Le fait est que vous pouvez obtenir une introspection en utilisant, euh, l'introspection. C'est ce qui se produit généralement:

  • Activez la réflexion.
  • Ajoutez une grande bibliothèque d'introspection à votre projet.
  • Marquez diverses méthodes et propriétés d'objet avec des attributs ou des annotations.
  • Laissez la bibliothèque d'introspection faire la magie.

En d'autres termes, si vous n'avez jamais besoin d'interfacer avec FP, vous n'avez pas à suivre les conseils de Rich Hickey.

Dernier point, mais pas le moindre (ni le plus joli), bien que l'utilisation Stringcomme clé de propriété ait le sens le plus simple, vous n'avez pas besoin d'utiliser le Strings. De nombreux systèmes hérités, y compris Android ™, utilisent largement les identifiants entiers dans l'ensemble du cadre pour faire référence aux classes, propriétés, ressources, etc.

Android est une marque déposée de Google Inc.


Vous pouvez également rendre les deux mondes heureux.

Pour le monde Java, implémentez les getters et setters comme d'habitude.

Pour le monde de la PF, implémentez le

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

À l'intérieur de ces fonctions, oui, du code laid, mais il y a des plugins IDE qui le rempliront pour vous, en utilisant ... euh, un plugin intelligent qui lit votre code.

Le côté Java des choses sera aussi performant que d'habitude. Ils n'utiliseront jamais cette laideur du code. Vous voudrez peut-être même le cacher à Javadoc.

Le côté FP du monde peut écrire le code "leet" qu'il veut, et il ne vous crie généralement pas que le code est lent.


En général, l'utilisation d'une carte (sac de propriété) à la place d'un objet est courante dans le développement de logiciels. Il n'est pas propre à la programmation fonctionnelle ou à tout type de langage particulier. Ce n'est peut-être pas une approche idiomatique pour une langue donnée, mais certaines situations l'exigent.

En particulier, la sérialisation / désérialisation nécessite souvent une technique similaire.

Juste quelques réflexions générales concernant la "carte comme objet".

  1. Vous devez toujours fournir une fonction pour valider une telle "carte en tant qu'objet". La différence est que "mapper en tant qu'objet" permet des critères de validation plus flexibles (moins restrictifs).
  2. Vous pouvez facilement ajouter des champs d'addition à la "carte en tant qu'objet".
  3. Pour fournir une spécification de l'exigence minimale d'un objet valide, vous devrez:
    • Énumérer le jeu de clés «minimalement requis» attendu sur la carte
    • Pour chaque clé dont la valeur doit être validée, fournissez une fonction de validation de valeur
    • S'il existe des règles de validation qui doivent vérifier plusieurs valeurs de clé, fournissez-les également.
    • Quel est l'avantage? Fournir la spécification de cette manière est introspectif: vous pouvez écrire un programme pour interroger le jeu de clés minimalement requis et pour obtenir la fonction de validation pour chaque clé.
    • Dans la POO, tous ces éléments sont regroupés dans une boîte noire, au nom de "l'encapsulation". Au lieu d'une logique de validation lisible par machine, l'appelant ne peut lire que la "documentation API" lisible par l'homme (si elle existe heureusement).
rwong
la source
commonplaceme semble un peu fort. Je veux dire qu'il est utilisé comme vous le décrivez, mais c'est aussi une de ces choses notoirement non délicates / fragiles (comme des tableaux d'octets ou des pointeurs nus) que les bibliothèques essaient de cacher.
Telastyn
@Telastyn Cette "tête laide de mille serpents" se produit généralement à la frontière de communication entre deux systèmes, où pour une raison quelconque le canal de communication ou interprocessus ne permet pas aux objets d'être téléportés intacts. Je suppose que de nouvelles techniques telles que les tampons de protocole ont presque éliminé ce cas d'utilisation archaïque de la carte en tant qu'objet. Il peut y avoir encore d'autres cas d'utilisation valides, mais je ne le sais pas.
rwong
2
Quant aux inconvénients fatals, d'accord. Mais, si les noms de clé de propriété "facile à mal orthographier" et "difficile à refactoriser" sont conservés, autant que possible, dans des constantes ou des énumérations , ce problème disparaît. Bien sûr, cela limite l'extensibilité :-(.
user949300
Si «l'inconvénient fatal» est vraiment fatal, pourquoi certaines personnes sont-elles capables de l'utiliser efficacement? De plus, les classes et le typage statique sont orthogonaux - vous pouvez définir des classes dans Clojure, même s'il est typé dynamiquement.
Nathan Davis
@NathanDavis (1) J'admets que ma réponse est écrite dans une perspective de frappe statique (C #) et j'ai écrit cette réponse parce que je partage le même point de vue du demandeur. J'avoue que je manque d'un point de vue centré sur la PF. (2) Bienvenue à SE.SE, et puisque vous êtes une figure respectée de Clojure, veuillez prendre le temps d'écrire votre propre réponse si les réponses existantes ne sont pas satisfaisantes. Les votes à la baisse soustraient les réputations et les nouvelles réponses attirent les votes à la hausse, ce qui ajoute rapidement les réputations. (3) Je peux voir comment les "objets incomplets" peuvent être utiles - vous pouvez interroger 2 propriétés pour un objet donné (nom, avatar) et laisser de côté le reste.
rwong
9

C'est un excellent discours de quelqu'un qui sait vraiment de quoi il parle. Je recommande aux lecteurs de regarder le tout. Cela ne dure que 36 minutes.

Un de ses principaux points est que la simplicité ouvre plus tard des opportunités de changement. Le choix d'une classe pour représenter un Personoffre l'avantage immédiat de créer une API statiquement vérifiable, comme vous l'avez souligné, mais cela s'accompagne du coût de la limitation des opportunités ou de l'augmentation des coûts de changement et de réutilisation ultérieurement.

Son point de vue est que l'utilisation de la classe pourrait être un choix raisonnable, mais cela devrait être un choix conscient qui vient avec une pleine conscience de son coût, et les programmeurs font traditionnellement un très mauvais travail de remarquer ces coûts, sans parler de les prendre en considération. Ce choix doit être réévalué à mesure que vos besoins augmentent.

Voici quelques changements de code (dont un ou deux ont été mentionnés dans l'exposé) qui sont potentiellement plus simples à l'aide d'une liste de cartes par rapport à l'utilisation d'une liste d' Personobjets:

  • Envoi d'une personne à un serveur REST. (Une fonction créée pour mettre une Mapdes primitives dans un format transmissible est hautement réutilisable et peut même être fournie dans une bibliothèque. Un Personobjet est susceptible d'avoir besoin de code personnalisé pour accomplir le même travail).
  • Construisez automatiquement une liste de personnes à partir d'une requête de base de données relationnelle. (Encore une fois, une fonction générique et hautement réutilisable).
  • Générez automatiquement un formulaire pour afficher et modifier une personne.
  • Utilisez des fonctions communes pour travailler avec des données personnelles très non homogènes, comme un étudiant par rapport à un employé.
  • Obtenez une liste de toutes les personnes qui résident dans un certain code postal.
  • Réutilisez ce code pour obtenir une liste de toutes les entreprises dans un certain code postal.
  • Ajoutez un champ spécifique au client à une personne sans affecter les autres clients.

Nous résolvons ces types de problèmes tout le temps, et avons des modèles et des outils pour eux, mais nous nous arrêtons rarement pour penser si le choix d'une représentation de données plus simple et plus flexible au début aurait facilité notre travail.

Karl Bielefeldt
la source
Y a-t-il un nom pour cela? Disons, mappage de propriété d'objet ou mappage d'attribut d'objet (le long de la même ligne que l'ORM)?
rwong
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.Mauvais et incroyablement malhonnête. Cela améliore vos chances de changer plus tard, car lorsque vous effectuez un changement de rupture, le compilateur trouvera et vous indiquera automatiquement chaque endroit qui doit être mis à jour pour mettre à jour votre base de code entière. C'est en code dynamique, où vous ne pouvez pas faire ça, que vous vous associez vraiment aux choix précédents!
Mason Wheeler
4
@MasonWheeler: Ce que vous dites vraiment, c'est que vous appréciez la sécurité du type à la compilation par rapport aux structures de données plus dynamiques (et plus lâches).
Robert Harvey
1
Le polymorphisme n'est pas un concept limité à la POO. Dans le cas des cartes, vous pouvez avoir un polymorphisme inclusif (si les éléments sont des sous-types d'un type que la carte peut gérer) ou un polymorphisme ad hoc (si les éléments sont des unions étiquetées). Ce sont des internes. Les opérations pouvant être effectuées sur une carte peuvent également être polymorphes. Polymorphisme paramétrique lorsque nous utilisons une fonction d'ordre supérieur sur les éléments ou ad hoc lors de la répartition. L'encapsulation peut être réalisée avec des espaces de noms ou d'autres formes de gestion de la visibilité. Fondamentalement, l'isolement des objets n'équivaut pas à affecter des opérations aux types de données.
siefca
1
@ GillBates pourquoi dites-vous cela? Vous perdez juste l'occasion de mettre ces méthodes virtuelles "à l'intérieur de la carte" - mais c'est exactement ce dont parle Rich Hickey, "ActiveObjects" sont vraiment un anti-modèle. Vous devez traiter les données comme ce qu'elles sont (données) et ne pas les mêler à un comportement. Il y a d'énormes avantages en termes de simplicité à séparer les préoccupations.
Virgil
4
  • Si les données ont peu ou pas de comportement, avec un contenu flexible susceptible de changer, utilisez une carte. L'OMI, un "javabean" ou "objet de données" typique qui se compose d'un modèle de domaine anémique avec N champs, N setters et N getters, est une perte de temps. N'essayez pas d'impressionner les autres avec votre structure glorifiée en l'enveloppant dans une classe de fantaisie fantaisie. Soyez honnête, expliquez clairement vos intentions et utilisez une carte. (Ou, si cela a un sens pour votre domaine, un objet JSON ou XML)

  • Si les données ont un comportement réel significatif, c'est-à-dire des méthodes ( Tell, Don't Ask ), utilisez une classe. Et tapotez-vous dans le dos pour utiliser une vraie programmation orientée objet :-).

  • Si les données ont un comportement de validation essentiel et des champs obligatoires, utilisez une classe.

  • Si les données ont un comportement de validation modéré, c'est limite.

  • Si les données déclenchent des événements de changement de propriété, c'est en fait plus facile et beaucoup moins fastidieux avec une carte. Écrivez simplement une petite sous-classe.

  • Un inconvénient principal de l'utilisation d'une carte est que l'utilisateur doit convertir les valeurs en chaînes, en entiers, en faux, etc. Si cela est très ennuyeux et sujet aux erreurs, envisagez une classe. Ou considérez une classe d'assistance qui enveloppe la carte avec les getters appropriés.

user949300
la source
1
En fait, ce que Rich Hickey soutient, c'est que si les données ont un comportement réel significatif ... vous faites probablement tout le mal de la "conception". Les données sont des "informations". Dans le monde réel, l'information n'est PAS "un endroit où les données sont stockées". Les informations n'ont pas "d'opérations qui contrôlent la façon dont les informations changent". Nous ne transmettons pas d'informations en disant aux gens où elles sont stockées. Les métaphores orientées objet sont PARFOIS un modèle approprié du monde ... mais le plus souvent, elles ne le sont pas. C'est ce qu'il dit - "pensez au problème de ypur". Tout n'est pas un objet - peu de choses le sont.
Virgil
0

L'API pour a mapa deux niveaux.

  1. L'API pour les cartes.
  2. Les conventions de l'application.

L'API peut être décrite dans la carte par convention. Par exemple, la paire :api api-validatepeut être placée sur la carte ou :api-foo validate-fooêtre la convention. La carte peut même stocker api api-documentation-link.

L'utilisation de conventions permet au programmeur de créer un langage spécifique au domaine qui standardise l'accès à travers les "types" mis en œuvre sous forme de cartes. L'utilisation (keys map)permet de déterminer les propriétés lors de l'exécution.

Il n'y a rien de magique dans les cartes et il n'y a rien de magique dans les objets. Tout est expédié.

ben rudgers
la source