"Usa la mappa anziché la classe per rappresentare i dati" -Rich Hickey

18

In questo video di Rich Hickey , il creatore di Clojure, consiglia di utilizzare la mappa per rappresentare i dati anziché usando una classe per rappresentarlo, come fatto in Java. Non capisco come possa essere migliore, dal momento che l'utente API può 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, in che modo l'utente API può sapere quali sono gli input per creare una persona?

    
posta Emil 06.02.2015 - 09:24
fonte

4 risposte

11

Sommario exagg'itive (TM)

Ottieni alcune cose.

  • Ereditarietà del prototipo e clonazione
  • 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 specifiche) avranno proprietà extra "facoltative".
  • Introspezione di proprietà, vecchie e nuove
  • Introspezione delle regole di convalida (discusse di seguito)

C'è uno svantaggio fatale.

  • Il compilatore non controlla le stringhe errate per te.
  • Gli strumenti di refactoring automatico non cambieranno il nome delle chiavi di proprietà per te, a meno che non paghiate per quelli fantasiosi.

Il fatto è che puoi ottenere l'introspezione usando, uhm, l'introspezione. Questo è quello che succede di solito:

  • Abilita riflessione.
  • Aggiungi una grande libreria di introspezione nel tuo progetto.
  • Contrassegna vari metodi e proprietà dell'oggetto con attributi o annotazioni.
  • Lascia che la libreria di introspezione faccia la magia.

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

Ultimo, ma non il minimo (né il più bello), anche se l'utilizzo di String come chiave di proprietà rende il senso più diretto, non devi usare String s. Molte eredità i sistemi, tra cui Android ™, utilizzano estensivamente 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, implementa getter e setter come al solito.

Per il mondo FP, implementa

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

All'interno di questa funzione, 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 come al solito. Non useranno mai quella brutta parte del codice. Potresti persino volerlo nascondere da Javadoc.

Il lato FP del mondo può scrivere qualunque codice "leet" che vogliono, e in genere non ti urla del fatto che il codice sia lento.

In generale, l'utilizzo di una mappa (sacchetto di proprietà) al posto dell'oggetto è comune nello sviluppo del software. Non è univoco per la programmazione funzionale o per particolari tipi di linguaggi. 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 "mappa come oggetto".

  1. Devi ancora fornire una funzione per convalidare una "mappa come oggetto". La differenza è che "mappa come oggetto" consente criteri di convalida più flessibili (meno restrittivi).
  2. Puoi facilmente aggiungere campi di addizione alla "mappa come oggetto".
  3. Per fornire una specifica del requisito minimo di un oggetto valido, dovrai:
    • Elenca il set di chiavi "minimamente necessario" 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 questo.
    • Qual è il vantaggio? Fornire la specifica in questo modo è introspettivo: è possibile scrivere un programma per interrogare il set di chiavi minimo necessario e ottenere la funzione di convalida per ogni chiave.
    • In OOP, tutti questi sono arrotolati 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).
risposta data 06.02.2015 - 15:05
fonte
9

Questa è un'ottima conversazione di qualcuno che sa davvero di cosa sta parlando. Raccomando ai lettori di guardare l'intera cosa. Ha solo 36 minuti.

Uno dei suoi punti principali è che la semplicità apre opportunità di cambiamento in seguito. La scelta di una classe per rappresentare un Person offre il vantaggio immediato di creare un'API verificabile staticamente, 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 viene fornita con piena consapevolezza dei suoi costi, e i programmatori fanno tradizionalmente un lavoro molto scarso di notare quei costi, per non parlare di prenderli in considerazione. Questa scelta dovrebbe essere rivalutata man mano che le tue esigenze crescono.

Le seguenti sono 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 oggetti Person :

  • Invio di una persona a un server REST. (Una funzione creata per mettere un Map di primitive in un formato trasmissibile è altamente riutilizzabile e potrebbe anche essere fornita in una libreria. Un oggetto Person ha probabilmente bisogno di codice personalizzato per realizzare lo stesso lavoro).
  • Costruisci automaticamente un elenco di persone da una query di database relazionale. (Di nuovo, una funzione generica e altamente riutilizzabile).
  • Genera automaticamente un modulo per visualizzare e modificare una persona.
  • Utilizza le funzioni comuni per lavorare con dati di persone che sono altamente non omogenei, come uno studente rispetto a 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 attività commerciali in un determinato codice postale.
  • Aggiungi un campo specifico del cliente a una persona senza intaccare altri clienti.

Risolviamo continuamente questo tipo di problemi e disponiamo di schemi 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 reso il nostro lavoro più semplice.

    
risposta data 07.02.2015 - 00:00
fonte
4
  • Se i dati hanno un comportamento scarso o assente, con contenuti flessibili che potrebbero cambiare, utilizzare una mappa. IMO, un tipico "javabean" o "oggetto dati" costituito da un modello di dominio anemico con campi N, N setter e N getters, è una perdita di tempo. Non cercare di impressionare gli altri con la tua struttura glorificata avvolgendola in una classe di fantasia stravagante. Sii onesto, rendi chiare le tue intenzioni , e usare una mappa. (Oppure, se ha senso per il tuo dominio, un oggetto JSON o XML)

  • Se i dati hanno un comportamento effettivo significativo, ovvero metodi ( Tell, Do not Ask ), quindi usa una classe. E datti una pacca sulla schiena per usare la vera programmazione orientata agli oggetti: -).

  • Se i dati hanno un sacco di comportamenti di convalida essenziali e campi obbligatori, usa una classe.

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

  • Se i dati generano eventi di modifica delle proprietà, ciò è in realtà più semplice e molto meno noioso con una mappa. Basta scrivere una piccola sottoclasse.

  • Uno svantaggio principale dell'uso di una mappa è che l'utente deve eseguire il cast dei valori su stringhe, int, Foos, ecc. Se ciò è altamente fastidioso e soggetto a errori, prendere in considerazione una classe. Oppure considera una classe helper che avvolge la mappa con i getter pertinenti.

risposta data 07.02.2015 - 01:44
fonte
0

L'API per map ha 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-validate può essere inserita nella mappa o :api-foo validate-foo potrebbe essere la convenzione. La mappa può anche memorizzare api api-documentation-link .

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

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

    
risposta data 07.02.2015 - 00:36
fonte

Leggi altre domande sui tag