Idea buona o cattiva per creare un costruttore di factory statico "fromJSON" per un oggetto che viene spesso creato analizzando un JSON proveniente da API?

4

Ho una classe POJO "Prodotto" nella mia app. Un oggetto Product può essere creato in-app dall'utente o analizzando un json proveniente dall'API.

Il prodotto ha più di 20 campi, ma io sto usando solo 2 qui per chiarezza

Esempio:

JSON productJSON = networkCommunicator.getJSONFromStream();

Product product = new Product();
product.setId(productJSON.getInt("id");
product.setName(productJSON.getString("name");

Voglio creare un costruttore fromJSON nella classe Product, ma non so se sia una buona idea, perché collega la classe Product all'implementazione API della struttura JSON e ai nomi delle chiavi nel JSON:

class Product {

    private int id;
    private String name;


    public Product(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public static Product fromJSON(JSONObject jsonObject) {
        return new Product(jsonObject.getInt("id"), jsonObject.getString("name");
    }

}

Questo renderà l'analisi del JSON proveniente dal server molto più pulito.

    
posta J. K. 10.01.2015 - 13:41
fonte

3 risposte

2

Qui entrerai nelle guerre di religione, ma lasciami dire che sono sul lato "ignora l'SRP" su questo. Sì, stai accoppiando a uno specifico schema di serializzazione, come correttamente indicato da @amon. Ma, in questo esempio, si sta accoppiando a uno schema astratto e standard , che lo rende "o.k.". Molte classi Java hanno valueOf(String) e tutte hanno toString() e il mondo non è terminato a causa della mancanza di SRP.

L'SRP esiste non perché lo stesso SRP sia un obiettivo, ma perché SRP riduce l'effetto del cambiamento, un obiettivo ammirevole. In questo esempio, cosa è più probabile che cambi?

  1. Product , ad es. aggiungendo un nuovo campo chiamato newFeature , cambiandolo in un array, ecc ... o
  2. Specifica e libreria JSON che usi.

Nella mia esperienza, "# 1" è sempre la risposta giusta. YMMV.

Se Product cambia e fromJSON() è un metodo di Product , one modifiche di classe. E stava per cambiare comunque, quindi non c'è un cambiamento "extra". Inoltre, potresti mantenere i nuovi campi di Product privato e finale , aumentando l'occultamento dei dati e la sicurezza dei thread.

OTOH, se stai utilizzando un ProductBuilder o ProductSerializer layer, allora due cambiano le classi. Stai aggiungendo cose su newFeature in più posti, il che viola DRY . È più difficile mantenere il nuovo campo privato e definitivo. È semplicemente lavoro extra e tedio con poco o nessun vantaggio , violando LEAN o YAGNI . Vedi, posso anche citare dei principi. : -)

Un'altra cosa: considera la possibilità di fare di fromJSON un metodo di istanza reale, non una funzione di classe statica. (Restituisce ancora un nuovo oggetto del prodotto) Sì, sembra strano e potresti decidere di non farlo. Ma "ci abbiamo provato" in un unico progetto ed è stato meraviglioso. Potrebbe essere necessario aggiungere un final static Product.PROTOTYPE (o simile, magari chiamandolo builder?). Ma, apre alcune buone caratteristiche:

  1. Può entrare in un'interfaccia, JSONable . I metodi statici non possono essere nell'interfaccia
  2. Ora puoi avere un Collection<JSONable> che improvvisamente fa molto per te.
  3. Puoi fornire una versione di prova, una versione fittizia, ecc. Se esiste una classe per cui @amon ha ragione e vuoi veramente disaccoppiare la logica in ProductBuilder , puoi chiamarla qui.

Avvertimenti :

Se cambi le librerie JSON, dovrai modificare molte delle tue classi, quindi assicurati di scriverle in modo intelligente in modo che il codice JSON sia astratto, come dovrebbe essere, e come tutte le librerie . È facile.

E, se stai serializzando su un formato strano, insolito, non standard, molto specifico e probabilmente modificabile, sì, allora sono d'accordo con @amon sul fatto che il codice debba essere separato. O stavi seguendo il JSON di qualcun altro e continuavano a cambiare il nome del loro campo, sarebbe un problema. Oppure, se l'analisi è estremamente complessa, è necessario combinare / "unire" un paio di stream JSON, ecc.

    
risposta data 10.01.2015 - 21:04
fonte
2

Non c'è niente che ti impedisca di farlo a livello pratico, IMO. Sì, tecnicamente è una violazione della separazione delle preoccupazioni, ma in modo pragmatico non è peggio di, ad esempio, il pattern Active Record, che è ampiamente utilizzato e molti non hanno alcun problema con esso.

In alternativa, come suggerito nei commenti, è possibile utilizzare un oggetto generatore separato per costruire gli oggetti:

 Product product = new ProductBuilder().setJSON(jspnObject).build ();

(l'implementazione di ProductBuilder dovrebbe essere ovvia)

Una terza opzione è l'uso di una libreria di associazione dati, ad es. Jackson.

    
risposta data 10.01.2015 - 15:17
fonte
1

Il problema con il tuo metodo Product.fromJSON è che associa il Product a uno schema di serializzazione specifico. Inoltre, questo viola il Principio di Responsabilità Unica in quanto la classe Product non riguarda solo la modellazione di qualcosa, ma anche lo scambio di dati.

In generale, ha senso isolare i modelli interni dallo scambio di dati esterni tramite un livello di de- e codifica. Invece di networkCommunicator.getJSONFromStream() , vogliamo qualcosa come connection.getProduct() . Se questa connessione utilizza JSON o XML o YAML è del tutto irrilevante per il tuo Product .

 YOUR   | TRANSLATION | OUTSIDE
 MODEL  |    LAYER    |  WORLD
        |             |
   .------- decode  <---- JSON
   v    |             |
Product |             |
   '------> encode  ----> JSON 
        |             |

Se ti capita di fare sviluppo web, probabilmente hai sentito parlare dell'architettura MVC (Model-View-Controller) o di qualche variante di esso. Il controller riceve l'input esterno e lo traduce in operazioni sul modello. Nel diagramma sopra, questo corrisponde al passo di decodifica nel livello di traduzione. Se una parte del modello viene visualizzata nel mondo esterno, il modello viene visualizzato dalla vista. Nel diagramma, questo è il passaggio di codifica.

Un tale livello di traduzione non è quasi mai eccessivo, dal momento che la rappresentazione dei dati esterni e il modello interno potrebbero essere diversi. Questo è anche il luogo perfetto per fare convalida sull'input esterno.

    
risposta data 10.01.2015 - 15:17
fonte

Leggi altre domande sui tag