web-dev-qa-db-fra.com

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

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?

19
Emil

Résumé exaggitif (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.

La chose est, vous pouvez obtenir l'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 de String comme clé de propriété soit la plus simple, vous n'avez pas besoin d'utiliser Strings. De nombreux systèmes hérités, y compris Android ™, utilisent largement les identifiants entiers dans l'ensemble du framework 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 FP, 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 IDE plugins 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 partie laide du code. Vous pourriez même vouloir pour 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 unique à 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 la "carte 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 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).
12
rwong

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 Person offre l'avantage immédiat de créer une API statiquement vérifiable, comme vous l'avez souligné, mais cela vient avec le coût de limiter les opportunités ou d'augmenter les coûts de changement et de réutilisation ultérieurement.

Son point de vue est que l'utilisation de la classe peut ê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 mappages par rapport à l'utilisation d'une liste d'objets Person:

  • Envoi d'une personne à un serveur REST. (Une fonction créée pour mettre un Map de primitives dans un format transmissible est hautement réutilisable et peut même être fournie dans une bibliothèque. A Person objet aura probablement 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 courantes 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.

9
Karl Bielefeldt
  • 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. 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, a.k.a méthodes ( Tell, Don't Ask ), puis utilisez une classe. Et tapotez-vous dans le dos pour utiliser une vraie programmation orientée objet :-).

  • Si les données ont beaucoup de 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'aide qui enveloppe la carte avec les getters appropriés.

4
user949300

L'API pour un map a 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-validate peut être placé sur la carte ou :api-foo validate-foo pourrait ê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. En utilisant (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é.

0
ben rudgers