La clonazione offre un miglioramento delle prestazioni rispetto ai metodi di costruzione / fabbrica?
-
20-08-2019 - |
Domanda
Sto mantenendo una vecchia base di codice Java (jvm 1.4) che sembra usare la clonazione come alternativa all'istanza di oggetti, immagino come ottimizzazione delle prestazioni. Ecco un esempio inventato:
public class Foo {
private SomeObject obj; // SomeObject implements Cloneable
public Foo() {
obj = new SomeObject();
obj.setField1("abc"); // these fields will have the same value every time
obj.setField2("def");
}
public void doStuff() {
SomeObject newObj = obj.clone(); // clone it instead of using a factory method
// do stuff with newObj
}
}
Nonostante i soliti avvertimenti sull'ottimizzazione prematura, a un certo punto questo è stato in realtà un linguaggio raccomandato?
Soluzione
Un motivo per invocare clone()
invece del costruttore della copia o di un metodo factory è che nessuna delle altre opzioni potrebbe essere disponibile.
L'implementazione di Cloneable
per eseguire una copia di oggetto superficiale (la copia di profondità è più coinvolta) è banale rispetto all'implementazione di un costruttore di copie o di un metodo di fabbrica per eseguire la stessa operazione. Per implementare super.clone()
, una classe deve semplicemente implementare l'interfaccia Object.clone()
e sostituire il metodo CloneNotSupportedException
con uno che invoca <=> che di solito invoca <=>. <=> copia ogni proprietà dell'oggetto originale nella corrispondente proprietà del duplicato, creando così una copia superficiale.
Sebbene l'implementazione di <=> sia semplice, è comunque facile dimenticare di implementare <=>. Di conseguenza, un potenziale rischio di utilizzo di <=> per duplicare un oggetto è che se la classe di quell'oggetto trascura di implementare <=> e <=> invoca <=> direttamente o indirettamente, si getta <=>.
Vedi questo esempio di codice e una precedente discussione su il design scadente di l'interfaccia <=>.
Altri suggerimenti
Presumibilmente volevano una copia. Forse vogliono passarlo a un'altra funzione e non possono essere sicuri che tale funzione non la cambierà. È un modo per assicurarsi che il metodo doStuff () sia const rispetto allo stato dell'oggetto Foo su cui è chiamato.
Un grave problema con i costruttori di copie è che il tipo di oggetto deve essere noto al momento della compilazione. Se una classe ereditabile supporta un costruttore di copie e al costruttore viene passato un oggetto di classe derivata, il costruttore produrrà un oggetto di classe base le cui proprietà di classe base generalmente corrispondono a quelle dell'oggetto passato, ma il nuovo oggetto vincerà ' t supporta tutte le funzionalità presenti nell'oggetto passato che non erano presenti nella classe base.
È possibile risolvere un po 'questo problema creando un costruttore di copie " protetto " e avendo un metodo di copia di fabbrica sostituibile in ogni classe derivata che chiama quel costruttore di copie della classe, che a sua volta chiama il costruttore della copia della sua classe base. Ogni classe derivata avrà bisogno di un costruttore di copia e di una sostituzione del metodo di copia, indipendentemente dal fatto che aggiunga o meno nuovi campi. Se la classe case usa & Quot; clone & Quot; questo codice aggiuntivo può essere eliminato.
Potrebbe essere un'ottimizzazione delle prestazioni, a seconda di quanto lavoro viene svolto nei costruttori.
È più probabile che venga utilizzato perché la semantica è diversa. La clonazione fornisce un modo per implementare & Quot; prototipo semantica & Quot; (come in javascript, self, ecc.) in una lingua che normalmente non tende in questo modo.
Se il costruttore SomeObject fa un lavoro costoso, come prendere qualcosa da un database o analizzare qualcosa o leggere qualcosa da un file, il clone avrebbe senso evitare di fare il lavoro.
Se il costruttore non fa nulla, non è necessario utilizzare il clone.
Modifica: aggiunto codice per mostrare che il clone non deve fare lo stesso lavoro del costruttore:
class Main
implements Cloneable
{
private final double pi;
public Main()
{
System.out.println("in Main");
// compute pi to 1,000,000,000 decimal palaces
pi = 3.14f;
}
public Object clone()
{
try
{
return (super.clone());
}
catch(final CloneNotSupportedException ex)
{
throw new Error(); // would not throw this in real code
}
}
public String toString()
{
return (Double.toString(pi));
}
public static void main(String[] args)
{
final Main a;
final Main b;
a = new Main();
b = (Main)a.clone();
System.out.println("a = " + a);
System.out.println("b = " + b);
}
}
Il costruttore principale viene chiamato una volta, il pi di calcolo viene eseguito una volta.