"Verwenden Sie eine Karte anstelle einer Klasse, um Daten darzustellen" -Rich Hickey

19

In diesem Video von Rich Hickey , dem Entwickler von Clojure, rät er dazu, Map zur Darstellung von Daten zu verwenden, anstatt eine Klasse zur Darstellung zu verwenden, wie dies in Java geschieht. Ich verstehe nicht, wie es besser sein kann, da wie der API-Benutzer wissen kann, was die Eingabetasten sind, wenn sie einfach als Karten dargestellt werden.

Beispiel :

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

In der zweiten Funktion, wie kann der API-Benutzer wissen, welche Eingaben erforderlich sind, um eine Person zu erstellen?

Emil
quelle
Das würde ich auch gerne wissen und ich habe das Gefühl, dass die Beispielfrage es nicht ganz beantwortet.
Sydney
Ich weiß, ich habe diese Diskussion schon einmal irgendwo auf SE gesehen. Ich glaube, es war im Kontext von JavaScript, aber die Argumente waren die gleichen. Kann es aber nicht finden.
Sebastian Redl
2
Nun, da Clojure ein Lisp ist, sollten Sie Dinge tun, die Lisp angemessen sind. Wenn Sie Java verwenden, codieren Sie ... na ja, Java.
AK_

Antworten:

12

Exagg'itive Summary (TM)

Sie bekommen ein paar Dinge.

  • Prototypische Vererbung und Klonen
  • Dynamisches Hinzufügen neuer Eigenschaften
  • Koexistenz von Objekten verschiedener Versionen (Spezifikationsebenen) derselben Klasse.
    • Objekte, die zu den neueren Versionen (Spezifikationsebenen) gehören, haben zusätzliche "optionale" Eigenschaften.
  • Introspektion alter und neuer Immobilien
  • Introspektion von Validierungsregeln (weiter unten besprochen)

Es gibt einen fatalen Nachteil.

  • Der Compiler sucht für Sie nicht nach falsch geschriebenen Zeichenfolgen.
  • Automatische Refactoring-Tools benennen Eigenschaftsschlüsselnamen für Sie nicht um - es sei denn, Sie zahlen für die ausgefallenen.

Die Sache ist, Sie können Introspektion erhalten, indem Sie, ähm, Introspektion verwenden. Das ist was normalerweise passiert:

  • Aktivieren Sie die Reflektion.
  • Fügen Sie Ihrem Projekt eine große Introspection-Bibliothek hinzu.
  • Markieren Sie verschiedene Objektmethoden und -eigenschaften mit Attributen oder Anmerkungen.
  • Lassen Sie die Introspektionsbibliothek die Magie machen.

Mit anderen Worten, wenn Sie nie eine Schnittstelle zu FP benötigen, müssen Sie Rich Hickeys Rat nicht befolgen.

Last but not least (oder am schönsten), obwohl die Verwendung Stringals Eigenschaftsschlüssel am einfachsten ist, müssen Sie nicht Strings verwenden. Viele ältere Systeme, einschließlich Android ™, verwenden im gesamten Framework ausführlich Ganzzahl-IDs, um auf Klassen, Eigenschaften, Ressourcen usw. zu verweisen.

Android ist eine Marke von Google Inc.


Sie können auch beide Welten glücklich machen.

Implementieren Sie für die Java-Welt die Getter und Setter wie gewohnt.

Implementieren Sie für die FP-Welt die

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

Innerhalb dieser Funktion, ja, hässlicher Code, aber es gibt IDE-Plugins, die das für Sie auffüllen, mit ... äh, einem intelligenten Plugin, das Ihren Code liest .

Die Java-Seite der Dinge wird genauso performant sein wie üblich. Sie werden diesen hässlichen Teil des Codes niemals verwenden . Vielleicht möchten Sie es sogar vor Javadoc verstecken.

Die FP-Seite der Welt kann jeden "leet" -Code schreiben, den sie wollen, und sie schreien Sie normalerweise nicht an, dass der Code langsam ist.


Im Allgemeinen ist die Verwendung einer Karte (Eigentumstasche) anstelle eines Objekts in der Softwareentwicklung üblich. Dies gilt nicht nur für die funktionale Programmierung oder bestimmte Arten von Sprachen. Es mag kein idiomatischer Ansatz für eine bestimmte Sprache sein, aber es gibt Situationen, die dies erfordern.

Insbesondere die Serialisierung / Deserialisierung erfordert oft eine ähnliche Technik.

Nur ein paar allgemeine Gedanken zu "Karte als Objekt".

  1. Sie müssen noch eine Funktion zur Validierung einer solchen "Karte als Objekt" bereitstellen. Der Unterschied besteht darin, dass "Karte als Objekt" flexiblere (weniger restriktive) Validierungskriterien ermöglicht.
  2. Sie können der "Karte als Objekt" problemlos weitere Felder hinzufügen.
  3. Um eine Spezifikation der Mindestanforderung eines gültigen Objekts bereitzustellen, müssen Sie:
    • Listen Sie die "minimal erforderlichen" Schlüssel auf, die in der Karte erwartet werden
    • Geben Sie für jeden Schlüssel, dessen Wert überprüft werden muss, eine Wertüberprüfungsfunktion an
    • Wenn es Validierungsregeln gibt, die mehrere Schlüsselwerte prüfen müssen, geben Sie dies ebenfalls an.
    • Was ist der vorteil Das Bereitstellen der Spezifikation auf diese Weise ist introspektiv: Sie können ein Programm schreiben, um den minimal erforderlichen Satz von Schlüsseln abzufragen und die Validierungsfunktion für jeden Schlüssel zu erhalten.
    • In OOP werden alle diese Elemente im Namen der "Verkapselung" zu einer Blackbox zusammengefasst. Anstelle einer maschinenlesbaren Validierungslogik kann der Aufrufer nur eine von Menschen lesbare "API-Dokumentation" lesen (sofern diese glücklicherweise vorhanden ist).
rwong
quelle
commonplacescheint mir ein wenig stark zu sein. Ich meine, es wird so verwendet, wie Sie es beschreiben, aber es ist auch eines dieser notorisch unscheinbaren / zerbrechlichen Dinge (wie Bytearrays oder bloße Zeiger), die Bibliotheken mit ihren verdammtesten Mitteln versuchen, sich zu verstecken.
Telastyn
@Telastyn Dieser "hässliche Kopf von tausend Schlangen" tritt normalerweise an der Kommunikationsgrenze zwischen zwei Systemen auf, wo aus irgendeinem Grund der Kommunikations- oder Interprozesskanal nicht zulässt, dass Objekte intakt teleportiert werden. Ich vermute, neue Techniken wie Protocol Buffers haben diesen archaischen Anwendungsfall der Karte als Objekt fast beseitigt. Es mag noch andere gültige Anwendungsfälle geben, aber ich habe wenig Kenntnis davon.
rwong
2
Stimmen Sie den schwerwiegenden Nachteilen zu. Wenn jedoch die Eigenschaftsschlüsselnamen "leicht zu buchstabieren" und "schwer zu refaktorisieren" in Konstanten oder Aufzählungen so weit wie möglich beibehalten werden , wird dieses Problem behoben . Natürlich schränkt es die Erweiterbarkeit einigermaßen ein :-(.
user949300
Wenn der "eine fatale Nachteil" wirklich fatale Folgen hat, warum können manche Leute ihn dann effektiv nutzen? Außerdem sind Klassen und die statische Typisierung orthogonal. Sie können Klassen in Clojure definieren, auch wenn sie dynamisch typisiert werden.
Nathan Davis
@ NathanDavis (1) Ich gebe zu, dass meine Antwort aus einer statischen Tippperspektive (C #) geschrieben wurde, und ich habe diese Antwort geschrieben, weil ich den gleichen Standpunkt des Fragestellers teile. Ich gebe zu, mir fehlt eine FP-zentrierte Sichtweise. (2) Willkommen bei SE.SE, und da Sie eine angesehene Persönlichkeit in Clojure sind, nehmen Sie sich bitte Zeit, Ihre eigene Antwort einzutragen, wenn die vorhandenen nicht zufriedenstellend sind. Downvotes subtrahieren die Reputation und neue Antworten ziehen Upvotes an, was die Reputation schnell erhöht. (3) Ich kann sehen, wie "unvollständige Objekte" nützlich sein können - Sie können 2 Eigenschaften für ein bestimmtes Objekt (Name, Avatar) abfragen und den Rest weglassen.
rwong
9

Das ist ein ausgezeichnetes Gespräch von jemandem, der wirklich weiß, wovon er spricht. Ich empfehle den Lesern, sich das Ganze anzuschauen. Es ist nur 36 Minuten lang.

Einer seiner Hauptpunkte ist, dass Einfachheit später Möglichkeiten für Veränderungen eröffnet. Die Auswahl einer Klasse zur Darstellung einer PersonAPI bietet den unmittelbaren Vorteil, eine statisch überprüfbare API zu erstellen, wie Sie bereits betont haben. Dies ist jedoch mit den Kosten verbunden, die durch die Begrenzung von Opportunities oder die Erhöhung der Kosten für Änderungen und spätere Wiederverwendung entstehen.

Sein Punkt ist, dass die Verwendung der Klasse eine vernünftige Wahl sein könnte, aber es sollte eine bewusste Wahl sein, die sich ihrer Kosten voll bewusst ist, und Programmierer leisten traditionell einen sehr schlechten Job darin, diese Kosten zu bemerken, geschweige denn sie zu berücksichtigen. Diese Wahl sollte neu bewertet werden, wenn Ihre Anforderungen wachsen.

Im Folgenden sind einige Codeänderungen aufgeführt (von denen eine oder zwei im Vortrag erwähnt wurden), die potenziell einfacher sind, wenn eine Liste von Karten verwendet wird, als wenn eine Liste von PersonObjekten verwendet wird:

  • Senden einer Person an einen REST-Server. (Eine Funktion, die erstellt wurde, um mehrere MapPrimitive in ein übertragbares Format zu setzen, ist in hohem Maße wiederverwendbar und kann sogar in einer Bibliothek bereitgestellt werden. Ein PersonObjekt benötigt wahrscheinlich benutzerdefinierten Code, um denselben Job auszuführen.)
  • Erstellen Sie automatisch eine Liste von Personen aus einer relationalen Datenbankabfrage. (Wieder eine generische und wiederverwendbare Funktion).
  • Generieren Sie automatisch ein Formular zum Anzeigen und Bearbeiten einer Person.
  • Verwenden Sie allgemeine Funktionen, um mit Personendaten zu arbeiten, die sehr inhomogen sind, z. B. Studenten oder Angestellte.
  • Holen Sie sich eine Liste aller Personen, die in einer bestimmten Postleitzahl wohnen.
  • Verwenden Sie diesen Code erneut, um eine Liste aller Unternehmen in einer bestimmten Postleitzahl zu erhalten.
  • Fügen Sie einer Person ein kundenspezifisches Feld hinzu, ohne andere Kunden zu beeinflussen.

Wir lösen diese Probleme ständig und verfügen über Muster und Werkzeuge, denken jedoch nur selten darüber nach, ob die Wahl einer einfacheren und flexibleren Datendarstellung am Anfang unsere Arbeit erleichtert hätte.

Karl Bielefeldt
quelle
Gibt es einen Namen dafür? Sagen wir, Objekt-Eigenschafts-Zuordnung oder Objekt-Attribut-Zuordnung (entlang derselben Linie wie 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.Falsch und unglaublich unaufrichtig. Dies verbessert die Möglichkeit für spätere Änderungen, da der Compiler bei Änderungen automatisch alle Stellen findet und darauf hinweist, die aktualisiert werden müssen, um die gesamte Codebasis auf den neuesten Stand zu bringen. Es ist in dynamischem Code, wo Sie das nicht können, dass Sie wirklich mit früheren Entscheidungen verbunden werden!
Mason Wheeler
4
@MasonWheeler: Was Sie wirklich sagen, ist, dass Sie Wert auf die Sicherheit beim Kompilieren legen, anstatt auf dynamischere (und weniger typisierte) Datenstrukturen.
Robert Harvey
1
Polymorphismus ist kein auf OOP beschränktes Konzept. Bei Karten können Sie einen Polymorphismus (wenn die Elemente Untertypen eines Typs sind, den die Karte verarbeiten kann) oder einen Ad-hoc-Polymorphismus (wenn die Elemente mit Gewerkschaften gekennzeichnet sind) haben. Das sind Interna. Die Operationen, die auf einer Karte ausgeführt werden können, können auch polymorph sein. Parametrischer Polymorphismus, wenn für Elemente Funktionen höherer Ordnung verwendet werden, oder Ad-hoc-Funktionen beim Versand. Die Kapselung kann mit Namespaces oder anderen Formen der Sichtbarkeitsverwaltung erfolgen. Grundsätzlich ist die Isolation von Objekten nicht gleichbedeutend mit der Zuweisung von Operationen zu Datentypen.
Siefca
1
@ GillBates warum sagst du das? Sie verpassen einfach die Gelegenheit, diese virtuellen Methoden "in die Map" zu integrieren - aber genau das ist es, worüber Rich Hickey spricht: "ActiveObjects" sind wirklich ein Anti-Pattern. Sie sollten Daten so behandeln, wie sie sind (Daten), und sie nicht mit Verhalten verflechten. Es gibt enorme Vorteile in Bezug auf die Einfachheit, die durch die Trennung von Bedenken erzielt werden können.
Virgil
4
  • Verwenden Sie eine Karte, wenn die Daten nur wenig oder gar kein Verhalten aufweisen und sich der flexible Inhalt wahrscheinlich ändert. IMO, ein typischer „javabean“ oder „Data Object“ , das aus einem besteht Anemic Domain Model mit N Feldern, N - Setter und N - Getter ist eine Verschwendung von Zeit. Versuchen Sie nicht, andere mit Ihrer verherrlichten Struktur zu beeindrucken, indem Sie sie in eine schicke Klasse einwickeln. Seien Sie ehrlich, machen Sie Ihre Absichten klar und verwenden Sie eine Karte. (Oder, wenn es für Ihre Domain sinnvoll ist, ein JSON- oder XML-Objekt)

  • Wenn die Daten ein signifikantes tatsächliches Verhalten aufweisen, auch bekannt als Methoden ( Tell, Don't Ask ), verwenden Sie eine Klasse. Und klopfe dir auf die Schulter, um echte objektorientierte Programmierung zu nutzen :-).

  • Verwenden Sie eine Klasse, wenn die Daten viele wichtige Validierungsfunktionen und erforderliche Felder aufweisen.

  • Wenn die Daten ein mäßiges Validierungsverhalten aufweisen, ist dies grenzwertig.

  • Wenn die Daten Eigenschaftsänderungsereignisse auslösen, ist dies mit einer Karte tatsächlich einfacher und weitaus weniger mühsam. Schreiben Sie einfach eine kleine Unterklasse.

  • Ein Hauptnachteil der Verwendung einer Map besteht darin, dass der Benutzer die Werte in Strings, ints, Foos usw. umwandeln muss. Wenn dies sehr ärgerlich und fehleranfällig ist, ziehen Sie eine Klasse in Betracht. Oder stellen Sie sich eine Hilfsklasse vor, die die Karte mit den relevanten Gettern umschließt.

user949300
quelle
1
Tatsächlich argumentiert Rich Hickey, dass, wenn die Daten ein signifikantes tatsächliches Verhalten aufweisen, Sie wahrscheinlich das ganze "Design" falsch machen. Daten sind "Informationen". Information ist in der realen Welt NICHT "ein Ort, an dem Daten gespeichert werden". Informationen haben keine "Vorgänge, die steuern, wie sich Informationen ändern". Wir übermitteln keine Informationen, indem wir Leuten mitteilen, wo sie gespeichert sind. Die objektorientierten Metaphern sind MANCHMAL ein geeignetes Modell der Welt ... aber meistens sind sie es nicht. Das sagt er - "denke über dein Problem nach". Nicht alles ist ein Objekt - es gibt nur wenige Dinge.
Virgil,
0

Die API für a maphat zwei Ebenen.

  1. Die API für Karten.
  2. Die Konventionen der Anwendung.

Die API kann gemäß Konvention in der Map beschrieben werden. Zum Beispiel kann das Paar :api api-validatein der Karte platziert werden oder :api-foo validate-fookönnte die Konvention sein. Die Karte kann sogar gespeichert werden api api-documentation-link.

Durch die Verwendung von Konventionen kann der Programmierer eine domänenspezifische Sprache erstellen, die den Zugriff über "Typen" hinweg standardisiert, die als Zuordnungen implementiert sind. Mit (keys map)können Eigenschaften zur Laufzeit bestimmt werden.

Karten haben nichts Magisches und Objekte nichts Magisches. Es ist alles Versand.

Ben Ruder
quelle