2010-11-16 14 views
7

Ho un codice che esegue una copia profonda utilizzando Object.clone, ma sto tentando di riscriverlo utilizzando la tecnica di costruzione di copia più "accettabile". Di seguito sono riportati due semplici esempi di ciò che sto cercando di fare, il primo che utilizza il clone e il secondo che utilizza un costruttore di copie.Modo corretto per eseguire una copia approfondita con il costruttore di copia anziché Object.clone

profonda copia utilizzando clone

import java.util.*; 

abstract class Person implements Cloneable { 
    String name; 
    public Object clone() throws CloneNotSupportedException { 
     return super.clone(); 
    } 
} 

class Teacher extends Person implements Cloneable { 
    int courses; 
    public String toString() { return name + ": courses=" + courses; } 
} 

class Student extends Person implements Cloneable { 
    double gpa; 
    public String toString() { return name + ": gpa=" + gpa; } 
} 

public class DeepCopy_Clone { 
    private static List<Person> deepCopy(List<Person> people) throws CloneNotSupportedException { 
     List<Person> copy = new ArrayList<Person>(); 
     for (Person person : people) { 
      copy.add((Person)person.clone()); 
     } 
     return copy; 
    } 

    public static void main(String[] args) throws CloneNotSupportedException { 
     ArrayList<Person> people = new ArrayList<Person>(); 

     Teacher teacher = new Teacher(); 
     teacher.name = "Teacher"; 
     teacher.courses = 5; 
     people.add(teacher); 

     Student student = new Student(); 
     student.name = "Student"; 
     student.gpa = 4.0; 
     people.add(student); 

     List<Person> peopleCopy = deepCopy(people); 

     // Invalidate the original data to prove a deep copy occurred 
     teacher.name = null; 
     teacher.courses = -1; 
     student.name = null; 
     student.gpa = -1; 

     for (Person person : peopleCopy) { 
      System.out.println(person.toString()); 
     } 
    } 
} 

profonda copia utilizzando costruttore di copia

import java.util.*; 

abstract class Person { 
    String name; 
    public Person() {} 
    public Person(Person other) { 
     this.name = other.name; 
    } 
    public Person deepCopy() { 
     if (this instanceof Teacher) { 
      return new Teacher((Teacher)this); 
     } else if (this instanceof Student) { 
      return new Student((Student)this); 
     } 

     throw new Error("Unknown type of person"); 
    } 
} 

class Teacher extends Person { 
    int courses; 
    public Teacher() {} 
    public Teacher(Teacher other) { 
     super(other); 
     this.courses = other.courses; 
    } 
    public String toString() { return name + ": courses=" + courses; } 
} 

class Student extends Person { 
    double gpa; 
    public Student() {} 
    public Student(Student other) { 
     super(other); 
     this.gpa = other.gpa; 
    } 
    public String toString() { return name + ": gpa=" + gpa; } 
} 

public class DeepCopy_ConstructorAlternative { 
    private static List<Person> deepCopy(List<Person> people) { 
     List<Person> copy = new ArrayList<Person>(); 
     for (Person person : people) { 
      copy.add(person.deepCopy()); 
     } 
     return copy; 
    } 

    public static void main(String[] args) { 
     ArrayList<Person> people = new ArrayList<Person>(); 

     Teacher teacher = new Teacher(); 
     teacher.name = "Teacher"; 
     teacher.courses = 5; 
     people.add(teacher); 

     Student student = new Student(); 
     student.name = "Student"; 
     student.gpa = 4.0; 
     people.add(student); 

     List<Person> peopleCopy = deepCopy(people); 

     // Invalidate the original data to prove a deep copy occurred 
     teacher.name = null; 
     teacher.courses = -1; 
     student.name = null; 
     student.gpa = -1; 

     for (Person person : peopleCopy) { 
      System.out.println(person.toString()); 
     } 
    } 
} 

Quello che trovo interessante è che, nonostante tutti i discorsi sui mali della clonazione in Java, il l'alternativa al clone richiede meno codice e meno cast (in questo caso particolare, almeno).

Apprezzerei il feedback sull'alternativa del costruttore di copie. Lo faresti diversamente? Grazie.

risposta

3

Invece di:

public Object clone() throws CloneNotSupportedException { 
    return super.clone(); 
} 

Preferirei:

public Person clone() { 
    try { 
     return (Person) clone(); 
    } catch (CloneNotSupportedException e) { 
     throw new RuntimeException("This should be impossible ..."); 
    } 
} 

così i chiamanti non c'è bisogno di gestire un'eccezione che non può mai accadere, e non c'è bisogno di lanciare.

Nell'approccio costruttore di copia, la commutazione tipo è meglio gestito polimorfico:

abstract class Person { 
    ... 
    public abstract Person deepCopy(); 
} 

class Student { 
    ... 
    public Student deepCopy() { 
     return new Student(this); 
    } 
} 

class Teacher { 
    ... 
    public Teacher deepCopy() { 
     return new Teacher(this); 
    } 
} 

ora il compilatore può controllare di aver fornito copia completa per tutti i sottotipi, e non è necessario alcun calchi.

Infine, si noti che sia l'approccio di clonazione sia quello di copia-costruttore hanno la stessa API pubblica (se il metodo è chiamato clone() o deepCopy() non importa molto), quindi quale approccio si utilizza è un dettaglio di implementazione. L'approccio costruttore di copia è più prolisso quando si forniscono sia un costruttore e un metodo di chiamare quel costruttore, ma può essere più facilmente generalizzato a una struttura generale del tipo di conversione, permettendo cose come:

public Teacher(Person p) { 
    ... 
    say("Yay, I got a job"); 
} 

Raccomandazione: utilizzare clone se si desidera solo una copia identica, utilizzare i costruttori di copie se il chiamante potrebbe desiderare di richiedere un'istanza di un tipo specifico.

1

Si prega di notare che nel Person.deepCopy dell'approccio del costruttore di copie, la classe Person deve testare esplicitamente tutte le sue sottoclassi. Questo è un problema fondamentale di progettazione, manutenzione e test del codice: impedirebbe la clonazione di successo se qualcuno introduce una nuova sottoclasse di Person, dimenticando o non essendo in grado di aggiornare Person.deepCopy. Il metodo .clone() evita questo problema fornendo un metodo virtuale (clone).

+2

Dimenticare o non essere in grado di aggiornare Person.deepCopy non sono problemi al confronto. Cioè, puoi affrontare problemi molto simili con l'alternativa clone. Ad esempio, se qualcuno crea una nuova "classe Administrator extends Person", dovrà ricordarsi di implementare Administrator.clone (assumendo che una copia field-by-field non esegua una copia profonda). Non essere in grado di aggiornare Person.deepCopy può essere gestito sovrascrivendolo nella sottoclasse. E sì, devi ricordarti di farlo, ma ancora una volta è lo stesso problema con l'alternativa clona. – vocaro

1

Un vantaggio di un approccio basato sui cloni è che, se implementati correttamente, i tipi derivati ​​che non richiedono essi stessi un comportamento speciale quando clonati non richiedono un codice di clonazione speciale. Per inciso, tendo a pensare che le classi che espongono un metodo di clonazione non dovrebbero generalmente essere ereditabili; invece, una classe base dovrebbe supportare la clonazione come metodo protetto e una classe derivata dovrebbe supportare la clonazione tramite un'interfaccia.Se un oggetto non supporta la clonazione, non dovrebbe generare un'eccezione da un'API Clone; invece, l'oggetto non dovrebbe avere un'API clone.

Problemi correlati