2012-03-05 2 views
12

Diciamo che ho una classe Foo in Java che dispone di dati immutabili:Java: metodo pseudo-setter per le classi immutabili

class Foo { 
    final private int x; 
    public int getX() { return this.x; } 
    final private OtherStuff otherstuff; 
    public Foo(int x, OtherStuff otherstuff) { 
     this.x = x; 
     this.otherstuff = otherstuff; 
    } 
    // lots of other stuff... 
} 

Ora mi piacerebbe aggiungere un metodo di utilità che crea un valore "fratello" con lo stato identico ma con un nuovo valore di x. Potrei chiamo setX():

class Foo 
{ 
    ... 
    Foo setX(int newX) { return new Foo(newX, this.otherstuff); } 
    ... 
} 

ma la semantica di setX() sono diversi rispetto alla convenzione standard setter per gli oggetti di fagioli mutabili, quindi in qualche modo questo non mi sembra giusto.

Qual è il nome migliore per questo metodo?

Devo chiamarlo withX() o newX() o qualcos'altro?


edit: nuova priorità nel mio caso: ho clienti di scripting (tramite JSR-223 e un modello a oggetti esporto) che può facilmente ottenere un oggetto Foo. È tuttavia ingombrante chiamare costruttori o creare costruttori o altro. Quindi è auspicabile che fornisca questo metodo come vantaggio per i client di scripting.

+0

Una questione collegata: http://stackoverflow.com/questions/521893/whats-the-best-name-for-a-non-mutating-add-method-on- un-immutabili-raccolta. (È lo stesso di questo, ma per 'add' invece di' setX'.) – ruakh

risposta

12

withX() è riuscita perché è una convenzione utilizzata per alcuni builder .

Questo è più di un "clone parziale" o "costruttore" di un "setter" ...

Se si guarda alla java.lang.String (anche immutabile) ci sono tutti i tipi di metodi che restituiscono una nuova stringa basata su quello vecchio (sottostringa, toLowerCase(), ecc) ...

Aggiornamento: Vedere anche rispondere da aioobe [deriveFoo()] che mi piace - è forse più chiaro, in particolare a tutti coloro che non hanno familiarità con builder .

1

E 'sicuramente non un setter, dal momento che in realtà costruisce e restituisce un nuovo oggetto. Credo che la semantica di fabbrica sarebbe l'opzione più appropriata in questo caso

public Foo newFooWith(int x) { 
    return new Foo(x, other); 
} 

L'alternativa potrebbe essere una variante del costruttore di copia

class Foo { 
    public Foo(Foo foo, int x) { 
     return new Foo(x, foo.getOtherStuff()); 
    } 
} 
+0

in che modo gli "othertuff" vengono copiati in questo caso? – DNA

+0

Sono d'accordo, ma in parte il mio problema deriva dai client di scripting, dove è facile ottenere un oggetto 'Foo' ma ingombrante per ottenere un oggetto' FooFactory'. –

+0

Questo non copierà altri dati di istanza –

12

Seguendo lo stile dello Font class nell'API standard lo chiamereo deriveFoo(int x).

Un'altra opzione sarebbe quella di fornire una classe builder che possa accettare un oggetto come prototipo per nuovi oggetti. In questi casi, il nome è setBar() come solo bar(). Questo darebbe qualcosa di simile

Foo newFoo = new Foo.Builder(foo).x(123).build(); 
+2

Mi piace questo approccio, +1 –

+0

Nel primo approccio suggerisci, come potresti derivare 'Foo' se ci fossero entrambi i campi' x' e 'y'' int'? –

+3

'java.awt.Font' è la classe JDK 1.0, molte decisioni di denominazione di quella versione non erano buone. JDK 8 usa la convenzione 'withFoo' nella nuova API' java.time', ad esempio ['java.time.YearMonth.withMonth()'] (http://download.java.net/jdk8/docs/api/java /time/YearMonth.html#withMonth-int-). – leventov

2

lo chiamerei withX(value). Dice che sarà qualcosa con x = value.

Se la classe aveva un sacco di campi, io avrei paura di:

obj.withX(1).withY(2).withZ(3).withU(1)... 

Quindi direi forse utilizzare il generatore di pattern di introdurre una variante mutabile della classe data con solo dati e metodi per creare la classe originale con il suo stato attuale. E li chiamerei questi metodi x(), y(), z() e li facciamo restituire this. Quindi sarebbe simile:

Immutable im2 = new Mutable(im1).x(1).y(2).z(3).build(); 
+0

Un approccio che può talvolta essere utile per rendere efficienti i metodi di stile 'with' anche quando gli elementi di dati sottostanti sono grandi e costosi da copiare è quello di aggiungere un ulteriore livello di riferimento indiretto al tipo di oggetto astratto sottostante e avere quel tipo astratto includere un metodo "appiattito" virtuale. Un metodo 'WithXX' può quindi restituire un nuovo wrapper che punta a un oggetto' WrapWithXX' che generalmente contiene un riferimento all'oggetto originale e il valore della nuova proprietà, a meno che non rilevi che la "catena" di oggetti sta diventando eccessivamente a lungo nel qual caso ... – supercat

+0

... chiamerebbe 'Flatten()'. Il metodo 'Flatten' genererebbe una nuova istanza dell'oggetto sottostante, prendendo in considerazione tutti i metodi' WithXX' che poteva analizzare. Chiamare "Appiattire" su un oggetto non cambierebbe il suo stato * osservabile *, ma sostituirebbe la sua realizzazione con una forma meno "complicata". Si noti che il livello extra di riferimento indiretto non sarebbe privo di costi, ma non sarebbe privo di vantaggi. Ad esempio, se due wrapper risultano avere oggetti distinti che confrontano identici, uno o entrambi i wrapper potrebbero essere modificati per puntare a un'istanza "canonica", accelerando i confronti. – supercat