"Usa la mappa invece della classe per rappresentare i dati" -Rich Hickey


19

In questo video di Rich Hickey , il creatore di Clojure, consiglia di usare la mappa per rappresentare i dati invece di usare una classe per rappresentarli, come fatto in Java. Non capisco come possa essere migliore, poiché come può l'utente API sapere quali sono le chiavi di input se sono semplicemente rappresentate come mappe.

Esempio :

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

Nella seconda funzione, come può l'utente API sapere quali sono gli input per creare una persona?



Mi piacerebbe anche saperlo e ritengo che la domanda di esempio non risponda del tutto.
Sydney

Io so che ho visto questa discussione prima da qualche parte su SE. Credo che fosse nel contesto di JavaScript, ma gli argomenti erano gli stessi. Non riesco a trovarlo però.
Sebastian Redl,

2
Bene, dato che Clojure è un Lisp, dovresti fare le cose appropriate per Lisp. quando usi Java, codice in ... bene Java.
AK_

Risposte:


12

Riepilogo esagerato (TM)

Ottieni alcune cose.

  • Eredità e clonazione prototipali
  • Aggiunta dinamica di nuove proprietà
  • Coesistenza di oggetti di versioni diverse (livelli di specifica) della stessa classe.
    • Gli oggetti appartenenti alle versioni più recenti (livelli di specifica) avranno proprietà "opzionali" extra.
  • Introspezione di proprietà, vecchie e nuove
  • Introspezione delle regole di convalida (discusso di seguito)

C'è un inconveniente fatale.

  • Il compilatore non controlla le stringhe errate per te.
  • Gli strumenti di refactoring automatico non rinomineranno i nomi delle chiavi di proprietà per te, a meno che tu non paghi per quelli fantasiosi.

Il fatto è che puoi ottenere l'introspezione usando, um, l'introspezione. Questo è ciò che accade di solito:

  • Abilita la riflessione.
  • Aggiungi una grande libreria di introspezione al tuo progetto.
  • Contrassegnare vari metodi e proprietà degli oggetti con attributi o annotazioni.
  • Lascia che la biblioteca dell'introspezione faccia la magia.

In altre parole, se non hai mai bisogno di interfacciarti con FP, non devi seguire il consiglio di Rich Hickey.

Ultimo, ma non meno importante (né il più grazioso), sebbene l'utilizzo Stringcome chiave di proprietà abbia il significato più semplice, non è necessario utilizzare Strings. Molti sistemi legacy, incluso Android ™, utilizzano ampiamente gli ID interi attraverso l'intero framework per fare riferimento a classi, proprietà, risorse, ecc.

Android è un marchio di Google Inc.


Puoi anche rendere felici entrambi i mondi.

Per il mondo Java, implementare getter e setter come al solito.

Per il mondo FP, implementare il

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

All'interno di queste funzioni, sì, brutto codice, ma ci sono plugin IDE che lo riempiranno per te, usando ... uh, un plugin intelligente che legge il tuo codice.

Il lato Java delle cose sarà altrettanto performante del solito. Non useranno mai quella brutta parte del codice. Potresti anche volerlo nascondere a Javadoc.

Il mondo FP del mondo può scrivere qualsiasi codice "leet" che vuole, e in genere non ti urlano che il codice sia lento.


In generale, l'utilizzo di una mappa (borsa delle proprietà) al posto dell'oggetto è all'ordine del giorno nello sviluppo del software. Non è unico per la programmazione funzionale o qualsiasi tipo particolare di linguaggio. Potrebbe non essere un approccio idiomatico per una determinata lingua, ma ci sono situazioni che lo richiedono.

In particolare, la serializzazione / deserializzazione richiede spesso una tecnica simile.

Solo alcuni pensieri generali riguardanti "la mappa come oggetto".

  1. Devi ancora fornire una funzione per convalidare una "mappa come oggetto". La differenza è che la "mappa come oggetto" consente criteri di convalida più flessibili (meno restrittivi).
  2. È possibile aggiungere facilmente campi di aggiunta alla "mappa come oggetto".
  3. Per fornire una specifica del requisito minimo di un oggetto valido, dovrai:
    • Elencare il set di chiavi "minimamente richiesto" previsto nella mappa
    • Per ogni chiave il cui valore deve essere convalidato, fornire una funzione di convalida del valore
    • Se esistono regole di convalida che devono controllare più valori chiave, fornire anche quello.
    • Qual è il vantaggio? Fornire le specifiche in questo modo è introspettivo: è possibile scrivere un programma per interrogare il set di chiavi minimamente richiesto e ottenere la funzione di validazione per ogni chiave.
    • In OOP, tutti questi vengono raggruppati in una scatola nera, nel nome di "incapsulamento". Al posto della logica di convalida leggibile dalla macchina, il chiamante può solo leggere "documentazione API" leggibile dall'uomo (se fortunatamente esiste).

commonplacemi sembra un po 'forte. Voglio dire, è usato come descrivi, ma è anche una di quelle cose notoriamente fragili / fragili (come array di byte o puntatori nudi) che le biblioteche fanno del loro meglio per nasconderle.
Telastyn,

@Telastyn Questa "brutta testa di mille serpenti" si verifica in genere sul confine di comunicazione tra due sistemi, dove per qualche ragione il canale di comunicazione o tra processi non consente il teletrasporto di oggetti intatti. Immagino che nuove tecniche come Protocol Buffers abbiano quasi eliminato questo caso di utilizzo arcaico della mappa come oggetto. Potrebbero esserci ancora altri casi d'uso validi, ma ne ho poca conoscenza.
rwong

2
Per quanto riguarda gli svantaggi fatali, d'accordo. Ma se i nomi delle chiavi delle proprietà "facili da scrivere" e "difficili da refactoring" vengono mantenuti, per quanto possibile, in costanti o enumerazioni , tale questione scompare. Certo, limita un po 'di estensibilità :-(.
user949300

Se l '"unico inconveniente fatale" è davvero fatale, perché alcune persone sono in grado di usarlo in modo efficace. Inoltre, le classi e la tipizzazione statica sono ortogonali: puoi definire le classi in Clojure, anche se è tipizzata in modo dinamico.
Nathan Davis,

@NathanDavis (1) Ammetto che la mia risposta è scritta da una prospettiva di scrittura statica (C #) e ho scritto questa risposta perché condivido lo stesso punto di vista del richiedente. Ammetto che mi manca un punto di vista incentrato sul PQ. (2) Benvenuto in SE.SE, e poiché sei una figura rispettata in Clojure, ti preghiamo di dedicare del tempo a scrivere nella tua risposta se quelli esistenti non sono soddisfacenti. I downgrade votano la reputazione e le nuove risposte attirano i voti, aumentando rapidamente la reputazione. (3) Vedo come "oggetti incompleti" possono essere utili: puoi interrogare 2 proprietà per un dato oggetto (nome, avatar) e tralasciare il resto.
rwong

9

È un discorso eccellente di qualcuno che sa davvero di cosa sta parlando. Consiglio ai lettori di guardare tutto. È lungo solo 36 minuti.

Uno dei suoi punti principali è che la semplicità apre in seguito opportunità di cambiamento. La scelta di una classe per rappresentare un Personoffre il vantaggio immediato di creare un'API staticamente verificabile, come hai sottolineato, ma ciò comporta il costo di limitare le opportunità o aumentare i costi per il cambiamento e il riutilizzo in seguito.

Il suo punto è che usare la classe potrebbe essere una scelta ragionevole, ma dovrebbe essere una scelta consapevole che arriva con la piena consapevolezza del suo costo, e i programmatori tradizionalmente fanno un pessimo lavoro nel notare quei costi, per non parlare della loro considerazione. Questa scelta dovrebbe essere rivalutata man mano che le tue esigenze crescono.

Di seguito sono riportate alcune modifiche al codice (una o due delle quali sono state menzionate nel talk) che sono potenzialmente più semplici utilizzando un elenco di mappe rispetto all'utilizzo di un elenco di Personoggetti:

  • Invio di una persona a un server REST. (Una funzione creata per mettere una Mapdelle primitive in un formato trasmissibile è altamente riutilizzabile e potrebbe anche essere fornita in una libreria. È Personprobabile che un oggetto abbia bisogno di un codice personalizzato per realizzare lo stesso lavoro).
  • Costruisci automaticamente un elenco di persone da una query di database relazionale. (Ancora una volta, una funzione generica e altamente riutilizzabile).
  • Genera automaticamente un modulo per visualizzare e modificare una persona.
  • Utilizzare funzioni comuni per lavorare con dati personali altamente non omogenei, come uno studente contro un dipendente.
  • Ottieni un elenco di tutte le persone che risiedono in un determinato codice postale.
  • Riutilizzare quel codice per ottenere un elenco di tutte le aziende in un determinato codice postale.
  • Aggiungi un campo specifico del cliente a una persona senza influire sugli altri clienti.

Risolviamo continuamente questo tipo di problemi e abbiamo modelli e strumenti per loro, ma raramente ci fermiamo a pensare se la scelta di una rappresentazione dei dati più semplice e flessibile all'inizio avrebbe facilitato il nostro lavoro.


C'è un nome per questo? Dire, Mappatura proprietà-oggetto o Mappatura oggetto-attributo (sulla stessa linea di 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.Sbagliato e incredibilmente disonesto. Si migliora la vostra opportunità per cambiare in seguito, perché quando si apporta una modifica di rottura, il compilatore troverà e sottolineare per voi ogni luogo che deve essere aggiornato per portare la vostra intera base di codice fino a velocità automaticamente. È nel codice dinamico, dove non puoi farlo, che ti accoppi davvero alle scelte precedenti!
Mason Wheeler,

4
@MasonWheeler: quello che stai davvero dicendo è che dai valore alla sicurezza del tipo in fase di compilazione rispetto alle strutture di dati più dinamiche (e più imprecise).
Robert Harvey,

1
Il polimorfismo non è un concetto limitato a OOP. Nel caso delle mappe potresti avere un polimorfismo inclusivo (se gli elementi sono sottotipi di qualche tipo che la mappa può gestire) o un polimorfismo ad hoc (se gli elementi sono unificati con tag). Questo è interno. Le operazioni che possono essere eseguite su una mappa possono anche essere polimorfiche. Polimorfismo parametrico quando utilizziamo la funzione di ordine superiore su elementi o ad hoc durante il dispacciamento. L'incapsulamento può essere ottenuto con spazi dei nomi o altre forme di gestione della visibilità. Fondamentalmente l'isolamento degli oggetti non equivale ad assegnare operazioni ai tipi di dati.
siefca,

1
@GillBates perché lo dici? Perdi semplicemente l'opportunità di mettere quei metodi virtuali "dentro la mappa" - ma questo è esattamente ciò di cui parla Rich Hickey, "ActiveObjects" è davvero un anti-pattern. Dovresti trattare i dati come sono (dati) e non intrecciarli con il comportamento. Ci sono enormi vantaggi in termini di semplicità da ottenere separando le preoccupazioni.
Virgilio,

4
  • Se i dati hanno un comportamento scarso o nullo, con contenuti flessibili che potrebbero cambiare, utilizzare una mappa. L'IMO, un tipico "javabean" o "oggetto dati" costituito da un modello di dominio anemico con N campi, N setter e N getter, è una perdita di tempo. Non cercare di impressionare gli altri con la tua struttura glorificata avvolgendola in una classe di fantasia. Sii onesto, chiarisci le tue intenzioni e usa una mappa. (O, se ha senso per il tuo dominio, un oggetto JSON o XML)

  • Se i dati hanno un comportamento effettivo significativo, ovvero metodi ( Tell, Don't Ask ), utilizzare una classe. E datti una pacca sulla spalla per usare la vera programmazione orientata agli oggetti :-).

  • Se i dati presentano molti comportamenti di convalida essenziali e campi obbligatori, utilizzare una classe.

  • Se i dati hanno una moderata quantità di comportamento di convalida, questo è limite.

  • Se i dati generano eventi di modifica delle proprietà, ciò è effettivamente più facile e molto meno noioso con una mappa. Scrivi solo una piccola sottoclasse.

  • Uno svantaggio principale dell'uso di una mappa è che l'utente deve trasmettere i valori a stringhe, ints, Foos, ecc. Se questo è molto fastidioso e soggetto a errori, considera una classe. Oppure considera una classe di supporto che avvolge la mappa con i getter pertinenti.


1
In realtà, ciò che Rich Hickey sostiene è che se i dati hanno un comportamento reale significativo ... probabilmente stai sbagliando l'intera cosa del "design". I dati sono "informazioni". L'informazione, nel mondo reale NON è "un luogo in cui i dati sono archiviati". Le informazioni non hanno "operazioni che controllano come le informazioni cambiano". Non trasmettiamo informazioni dicendo alle persone dove sono archiviate. Le metafore orientate agli oggetti SONO ALCUNI un modello appropriato del mondo ... ma il più delle volte non lo sono. Questo è quello che dice: "pensa al problema ypur". Non tutto è un oggetto - poche cose lo sono.
Virgilio,

0

L'API per a mapha due livelli.

  1. L'API per le mappe.
  2. Le convenzioni dell'applicazione.

L'API può essere descritta nella mappa per convenzione. Ad esempio la coppia :api api-validatepuò essere posizionata nella mappa o :api-foo validate-foopotrebbe essere la convenzione. La mappa può persino essere memorizzata api api-documentation-link.

L'uso delle convenzioni consente al programmatore di creare un linguaggio specifico del dominio che standardizzi l'accesso tra i "tipi" implementati come mappe. L'uso (keys map)consente di determinare le proprietà in fase di esecuzione.

Non c'è nulla di magico nelle mappe e non c'è niente di magico negli oggetti. È tutto spedito.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.