I metodi 'setX (Object o)' dovrebbero eseguire copie profonde o superficiali di oggetti?

1

La mia situazione particolare è legata a Java, ma credo che questa sia una domanda OOP più generale della solo programmazione Java.

La domanda: i metodi "mutator" dovrebbero eseguire copie profonde o superficiali ?

Un esempio:
Supponiamo che il mio oggetto abbia la seguente struttura:

public class MyObj {
  ArrayList<Integer> myList;
  //constructors, etc.

  public void setMyList(ArrayList<Integer> list) {
    this.myList = list; //Option 1
    //OR
    this.myList = new ArrayList<Integer>(list); //Option 2
  }
}

La mia ipotesi è che l'opzione 2 sia la migliore. Questo perché impedisce ad una classe esterna di avere accesso diretto alla lista di MyObj . Ad esempio, se l'ho fatto:

public class SomeOtherClass {
  public static void main(String[] args) {
    ArrayList<Integer> exampleList = new ArrayList<>(Arrays.asList(1, 2, 3));

    MyObj a = new MyObj();
    a.setMyList(exampleList);

    exampleList.add(4);
  }
}

Alla fine di main , l'opzione 1 significa che a.myList contiene 4 , mentre l'opzione 2 significa a.myList è ancora composta da 1, 2, 3 . Per me, il secondo sembra preferibile.

Esiste uno standard / convenzione riguardo a questa situazione?

    
posta apnorton 13.09.2014 - 04:28
fonte

2 risposte

4

Entrambi gli approcci possono essere corretti; dipende dalla semantica che vuoi. Fondamentalmente, la differenza è che la versione con la copia significa "imposta myList per contenere questi elementi: list ", mentre la versione senza la copia significa "imposta myList per essere questo oggetto lista: list ". Penso che le precedenti semantiche siano più spesso desiderabili (soprattutto perché le liste sono abbastanza "trasparenti": di solito pensiamo alle liste solo in termini di elementi che contengono, e non in termini di qualche "identità" stabile indipendente dai contenuti); ma ci sono usi validi per quest'ultimo.

Se segui l'approccio della copia, tra l'altro, ecco alcune cose da tenere a mente:

  • È una buona idea assicurarti che il tuo metodo get restituisca un elenco non modificabile, utilizzando Collections.unmodifiableList o qualcosa come ImmutableList.copyOf di Guava. (Nota che probabilmente non vuoi solo restituire new ArrayList<Integer>(myList) , perché sebbene questo protegga myList dall'essere modificato dal codice client, non rende ovvio che la modifica dell'elenco restituito non influisce l'oggetto originale. È meglio che myObj.getMyList().add(3) fallisca rumorosamente.)
  • List potrebbe non essere l'astrazione appropriata se non si desidera realmente che il codice client manipoli l'elenco come un elenco. E anche se il tuo getter restituisce List<Integer> , potrebbe essere appropriato che il setter accetti Iterable<Integer> .
risposta data 13.09.2014 - 04:45
fonte
3

Si noti che la stessa domanda si pone per i getter. Non copiare un campo mutabile in un getter significa esporre il contenuto per la modifica.

Stai parlando di shallow vs deep copy, ma frammento di show no vs copia superficiale. Una copia profonda sarebbe come

void setTwoLevelList(List<List<Integer>> twoLevelList) {
    this.twoLevelList = Lists.newArrayList();
    for (List l : twoLevelList) this.twoLevelList.add(Lists.newArrayList(l));
}

o ancora più complicato per strutture più profonde o non fattibile per qualcosa di simile

void setListOfT(List<T> t) {
    ??? how to copy T?
}

Il problema in Java è che non ha né una copia superficiale né una copia profonda. Ha clone , che è rotto in base alla progettazione e sottospecificato:

Creates and returns a copy of this object. The precise meaning of "copy" may depend on the class of the object.

Torna alla domanda

Quindi dovremmo fare no, shallow o deep copy nei setter?

Idealmente, non dovrebbero esserci setter. Gli oggetti immutabili sono i più facili da usare, ma sfortunatamente non è sempre possibile.

Non c'è una regola generale per i setter, eccetto che dovresti evitarli (anche più dello stato mutabile in generale). Spesso, un oggetto immutabile con un costruttore o un wither è un'alternativa.

Se devi scrivere un setter, allora ti consiglio di rendere il campo immutabile, ad esempio, usa un ImmutableList . Se anche i suoi membri sono immutabili, non c'è motivo di copiare nulla.

Altrimenti, coperei il più possibile . A volte, è necessario fare un'eccezione per motivi di prestazioni, ad esempio quando si passano spesso oggetti di grandi dimensioni. In un caso del genere è consigliabile rendere privato il pacchetto del metodo

Una buona soluzione è spesso qualcosa come

void setMyList(List<Integer> list) {
    this.list = ImmutableList.copyOf(list);
}
    
risposta data 13.09.2014 - 05:08
fonte

Leggi altre domande sui tag