Come copiare in sicurezza un oggetto?

3

Questa domanda sarà un po 'lunga. Per favore, sopportami.

Qualcosa che è accaduto in un mio progetto mi ha fatto pensare a come copiare in sicurezza gli oggetti. Presenterò la situazione che ho avuto e poi fare una domanda.

C'era una classe SomeClass :

class SomeClass {
    private Thing[] things;

    public SomeClass(Thing[] things){
        this.things = things;
    }

    // irrelevant stuff omitted

    public SomeClass copy(){
        return new SomeClass(things);
    }
}

C'era un'altra classe Processor che prende SomeClass oggetti, li copia (tramite someClassInstance.copy() ), manipola lo stato della copia e restituisce la copia. Eccolo:

class Processor{
    public SomeClass processObject(SomeClass object){
        SomeClass copy = object.copy();
        manipulateTheCopy(copy);
        return copy;
    }

    // irrelevant stuff omitted
}

Ho eseguito questo, e aveva dei bug. Ho esaminato questi bug e ho scoperto che le manipolazioni Processor su copy riguardano effettivamente non solo la copia, ma anche l'originale SomeClass oggetto passato in processObject .

Ho scoperto che era perché l'originale e lo stato di copia condivisa - perché l'originale ha passato il suo campo things nella copia durante la sua creazione.

Questo mi ha fatto capire che copiare oggetti è più difficile che istanziarli con gli stessi campi dell'originale.

Affinché i due oggetti siano completamente disconnessi, senza nessuno stato condiviso, anche ciascuno dei campi passati alla copia deve essere copiato. E se quell'oggetto contiene altri oggetti, anche questi devono essere copiati. E così via.

Quindi, in pratica, per poter effettivamente copiare un oggetto, ogni classe nel sistema deve avere un metodo copy() , che richiama anche copy() su tutti i suoi campi, e così via .

Quindi, per esempio, per copy() in SomeClass funzionare, deve assomigliare a questo:

public SomeClass copy() {
    Thing[] copyThings = new Thing[things.length];
    for(int i = 0; i < things.length; i++)
        copyThings[i] = things[i].copy();
    return new SomeClass(copyThings);
}

E se Thing ha i propri campi oggetto, il suo metodo copy() deve essere appropriato:

class Thing {
    private Apple apple;
    private Pencil pencil;
    private int number;

    public Thing(Apple apple, Pencil pencil, int number){
        this.apple = apple;
        this.pencil = pencil;
        this.number = number;
    }

    public Thing copy(){
        // 'number' is a primitve.
        return new Thing(apple.getCopy(), pencil.getCopy(), number);
    }
}

E così via.

Ovviamente, invece di tutte le classi che hanno un metodo copy() , il meccanismo di copia può accadere in tutti i getter e i costruttori di classi (a meno che non siano posti dove non è adatto, per esempio quando il campo punta a un oggetto esterno, non a un oggetto che 'fa parte' di questo oggetto).

Tuttavia, ciò significa che per poter copiare in modo sicuro un oggetto, la maggior parte delle classi dovrebbe avere meccanismi di copia nei loro getter.

La mia domanda è divisa in due parti:

  1. Con quale frequenza hai bisogno di ottenere una copia di un oggetto? Si tratta di un problema normale?

  2. La tecnica descritta è comune e / o ragionevole? O c'è un modo migliore per fare copie sicure degli oggetti?

posta Aviv Cohn 01.06.2014 - 23:48
fonte

3 risposte

3

Quello che stai descrivendo è un problema ben noto e molto basilare nei linguaggi dei computer che hanno oggetti. Non è davvero molto sorprendente. Il finale era abbastanza ovvio a circa metà della storia. È la differenza tra copia superficiale e copia profonda.

Gli oggetti tendono a ricadere in due categorie: quelli che rappresentano valori e quelli che hanno identità. Gli oggetti valore sono cose come punti e vettori e hanno davvero bisogno di essere copiati accuratamente. Se contengono riferimenti a oggetti mutabili hai bisogno di una copia profonda, ma fai attenzione. Potresti scavare una buca.

Gli oggetti di identità sono cose come widget, oggetti di business e altre entità complesse. Di solito non hanno bisogno di essere copiati. Per sicurezza, è meglio usare le funzionalità del linguaggio per far rispettare questo. [In C ++ utilizziamo un costruttore di copie privato.]

In risposta alle tue domande:

How frequently do you need to get a copy of an object? Is this a regular issue?

Valore oggetti comunemente; oggetti di identità raramente. È un problema a cui spesso devi pensare, ma spesso non fai nulla di speciale. Se stai copiando oggetti identici, potrebbe essere un odore.

Is the technique described common and/or reasonable? Or is there a better way to make safe copies of objects?

Dipende dalla lingua, ma nella maggior parte dei linguaggi sembra eccessivo. Assicurati che i tuoi oggetti valore siano copiati in modo sicuro, assicurati che i tuoi oggetti identità non possano essere copiati e gestiscano gli altri caso per caso.

Gli oggetti valore raramente contengono riferimenti ad altri oggetti e, se lo fanno, saranno solo un riferimento a un oggetto immutabile (come una stringa) oa un altro oggetto valore. Ad esempio, un oggetto Rectangle potrebbe contenere due oggetti Point. Questo è un caso in cui ha senso gestire una copia di un oggetto (Rettangolo) con una chiamata nidificata al metodo di copia di un altro oggetto (Punto). È davvero un'eccezione.

Si noti che C # è abbastanza utile in quanto ha tipi di valore reali. In Java e in altre lingue in cui tutti gli oggetti sono riferimenti, la vita non è così semplice.

Potresti trovarti in una situazione in cui sembra necessario copiare un oggetto che ha identità, anche se non dovrebbe essere comune. È necessario considerare attentamente i problemi di identità della copia (stessa o diversa), mutabilità (setter, ecc.), Se la copia può sostituire l'originale, ecc. È sempre caso per caso.

Nota anche che gli oggetti valore normalmente calcolano il loro valore hash dal loro contenuto, dove gli oggetti identità normalmente calcolano il loro valore hash da una caratteristica unica come il loro indirizzo. Ciò significa che potrebbero non funzionare bene con raccolte basate su hash come i dizionari.

    
risposta data 02.06.2014 - 07:12
fonte
2

Questa è una domanda interessante perché tocca alcuni concetti molto importanti che sono spesso confusi dai principianti e persino dai programmatori intermedi. Java tende ad esacerbare questa confusione in qualche modo a causa del design del linguaggio.

Il grosso problema qui è che inizialmente hai confuso la copia di un riferimento con la copia di un valore. Quello che pensavi di fare era copiare i valori, quello che hai finito è stato copiare i riferimenti.

Per spiegare la differenza, immagina di avere un televisore. Stai guardando un DVD su quella TV. Qualcun altro vuole guardare lo stesso programma come te. Quando copi per riferimento, stai dando loro una tv e collegando la tv al lettore DVD. Ogni volta che decidi di guardare un altro DVD, riescono a vedere cosa stai guardando. Se cambiano il DVD, vedi cosa stanno guardando.

Quando copi in base al valore, stai dando loro una TV, un lettore DVD e una copia del DVD. Possono cambiare il DVD a qualsiasi cosa piaccia e tu non lo vedi. Allo stesso modo, se cambi il DVD non vedranno ciò che stai guardando.

Ci sono molte volte quando vuoi copiare per valore e ci sono molte volte dove vuoi copiare per riferimento.

Per capire che tipo di copia vuoi fare, devi porsi le seguenti domande:

  1. Perché sto copiando questo oggetto?
  2. Per ciascuna delle proprietà dell'oggetto, desidero che le mie copie facciano riferimento alla proprietà originale o una copia della copia originale?
  3. Come si comporterà il mio programma se copio per riferimento / valore?

Le risposte a queste domande ti diranno quanto profondamente hai bisogno di copiare i tuoi oggetti.

    
risposta data 02.06.2014 - 04:50
fonte
0

1) Penso che dipenda dalla lingua, ma sì, copio un po 'le cose. Detto questo, penso che dipenda dal tipo di oggetto immensamente. Cose che tendo a copiare molto: semplici strutture di dati vecchi, cose come data / ora o altri oggetti che nel mio particolare programma sono elementi costitutivi di tutto il resto. Se si pensa alle relazioni oggettuali come ad albero, di solito copio solo i nodi foglia. (Pensa a struct o oggetti blitabili, nel vecchio significato di "blit"). Si noti che in molti casi questi nodi foglia sono anche buoni candidati per l'immutabilità (con un cenno a @ SJuan76). Cose che non copio: controller o servizi, qualsiasi cosa con riferimenti ad altri oggetti che non rientrano nella categoria del nodo foglia. Il tuo chilometraggio può variare.

2) Allora capiamo perché copi. Sfortunatamente, lo scopo di un oggetto copiato può essere molto diverso da un programma all'altro o anche all'interno di due parti di un programma. Il punto è che è abbastanza difficile ottenere una soluzione adatta a tutte le dimensioni perché non si programmerebbe se una dimensione si adattava a tutti. Ci sono almeno due approcci principali a questo: clonazione superficiale o profonda . @gnat ha sollevato un grande punto sul clone di Java. Allo stesso modo, vorrei fare un po 'di ricerche su membrowise clone di .NET. i suoi pro e contro. L'unica regola reale che ho trovato nella programmazione delle copie è che se si sceglie di copiare qualcosa di diverso da un nodo foglia, si corre un maggiore rischio di confusione su ciò che fa la copia - proprio come hai trovato dal tuo incarico.

    
risposta data 02.06.2014 - 03:57
fonte

Leggi altre domande sui tag