Article - Oggetti Immutabili

Share Embed Donate


Short Description

Pubblicato su Computer Programming, Ottobre 2004\ L’utilizzo di oggetti immutabili in Java si rivela essere un utile tec...

Description

Software Design

PROGRAMMING

L’utilizzo di oggetti immutabili in Java si rivela essere un utile tecnica di ottimizzazione in svariati contesti, oltre ad offrire un concreto utilizzo di meccanismi come il cloning e lo sharing di risorse.

Oggetti immutabili di Michele Arpaia Come è stato ben chiarito già ai primordi del movimento dei pattern di design [1], la scelta del linguaggio di programmazione che si andrà ad utilizzare per “codificare” i pattern è una forza del contesto che va bilanciata. Come dire, lo stesso pattern ha diverse incarnazioni perché deve dar conto ai costrutti che il linguaggio offre. Un pattern realizzato mediante un linguaggio di programmazione prende il nome di idioma. In questa articolo e nel prossimo prenderò in esame un idioma molto famoso presentando varie tecniche per realizzarlo, tecniche che di volta in volta ne daranno una caratterizzazione diversa in maniera tale da avere un campo di applicabilità molto vasto. Definizione Un oggetto è detto immutabile se lo stato che lo rappresenta non può essere cambiato dopo la sua creazione. Un noto esempio di oggetto immutabile è la classe java.lang.String. Una volta inizializzata la classe String non subirà variazioni di stato. Così ad esempio se String s=”immutabile”; il metodo seguente non altera il valore originale di s: public void changeString(String s) { s.toUpperCase(); s=s+” o mutabile?”; } Al metodo viene passata una copia del reference ad s, ma la classe String non ha metodi che alterino lo stato interno (mutators methods). Più precisamente i metodi che lavorano sullo stato della stringa restituiscono un oggetto dello stesso tipo con lo stato aggiornato. Un altro esempio di oggetti immutabili presenti nelle API Java sono le classi wrapper per i tipi primitivi.

Figura 1Differenza tra shallow copy e deep copy

Contesto Generalmente gli oggetti immutabili sono piccoli, hanno poche associazione con altri oggetti e pongono l’accento più sul contenuto (value based objects) che sull’identità dell’oggetto consentendo un potenziale resource sharing. Inoltre simulano in maniera semplice il passaggio per valore, utile in molti casi per migliorare l’occultamento dei dati.

Computer Programming n.139, Ottobre 2004

1

Software Design

PROGRAMMING

Un altro campo di applicabilità è dato dagli ambienti multithread ove gli oggetti immutabili sono la garanzia di thread-safe. Dato che non è possibile cambiare lo stato dell’oggetto, uno o più thread possono solo leggere lo stato evitando di sincronizzare le risorse con un notevole risparmio e guadagnando in performance. Come sempre è il progettista che pesa opportunamente la convenienza o meno di una siffatta soluzione, tenendo anche presente che alcune tecniche per realizzare l’immutability possono risultare pesanti (come ad esempio il cloning, che vedremo successivamente). Un altro vantaggio di avere oggetti immutabili è dato dal loro utilizzo nelle collezioni ordinate, ad esempio il TreeSet. Se inseriamo oggetti Account in questa collezione (avendo opportunamente implementato l’interfaccia Comparable) questi saranno mantenuti ordinati secondo il criterio scelto, che in questo caso semplice potremmo considerare l’ordinamento sul nome. Dato che Account è un oggetto mutabile, provando a modificare lo stato di un oggetto già inserito nella collection (attraverso il metodo setNome(), ad esempio), questa ultima non si accorgerà del nuovo stato che potrebbe richiedere un riordino degli elementi. Il problema si risolverebbe alla radice se Account fosse immutabile. Soluzione Possiamo stendere delle condizioni necessarie per la costruzioni di oggetti affinché questi risultino immutabili. Queste sono: • • • •

Dichiarazione final della classe Definizione private di tutti i data member Definizione di soli query methods Inizializzazione dei dati nei costruttori

Dichiarare la classe final evita il subclassing e di conseguenza la possibilità di dichiarare metodi mutators nelle sottoclassi. Non essendoci mutators methods il costruttore è l’unico modo per inizializzare la classe. I query methods rendono la classe a sola lettura. Le condizioni sopra elencate rappresentano un insieme necessario poiché una classe pur rispettandole potrebbe violare l’immutabilità. Ad esempio si consideri il caso di una classe Resource immutabile (secondo le condizioni sopra citate) che prende come parametro del costruttore un oggetto mutabile, ad esempio Account. In questo caso chiaramente qualsiasi modifica all’Account si riflette in una modifica dello stato del Resource. In teoria si potrebbe aggiungere alla lista delle condizioni quella di avere come parametri di input solo oggetti immutabili, ma questo restringerebbe di molto il campo di azione di questo idioma, poiché la creazione di un immutabile costringerebbe le classi utilizzate ad essere ugualmente immutabili. Un altro caso – in verità molto poco Object Oriented – è la restituzione di oggetti mutabili da parte dei metodi query di una classe. Questo approccio oltre a violare palesemente l’incapsulamento dei dati (restituisce una parte dello stato creando un eccessivo coupling con le classi client) è facilmente riconducibile al caso precedente. Prima tecnica: Cloning Le condizioni necessarie sopra citate diventano chiaramente sufficienti quando la classe a sua volta ha a che fare con oggetti immutabili custom, tipi primitivi e rispettivi wrapper, stringhe, e altri presenti nelle API Java. Nel caso in cui ci troviamo a lavorare con oggetti mutabili, una tecnica nativamente presente in Java è il cloning. Si tratta semplicemente di clonare gli oggetti passati al costruttore e quelli restituiti dai metodi getter. Riconsideriamo le classi Resource e Account, ove Resource è immutabile mentre Account non lo è. public final class Resource { Computer Programming n.139, Ottobre 2004

2

Software Design

PROGRAMMING

public Resource (String nome, Account acc) { …} // altri ctor public Account getAccount() { … return acc; } //altri metodi getter … } public class Account { public Account (String nome) {…} // altri ctor // altri metodi getter/setter … } Un frammento di codice che utilizza queste classi potrebbe essere: Account acc = new Account (“Administrator”); Resource res = new Resource (“PrinterHP”, acc); … acc.setName(“User”); E’ chiaro che Resource pur costruita per essere immutabile di fatto si trova a diventare mutabile a causa di Account. Il problema è che Resource riceve in input la copia del reference all’oggetto Account e non la copia dell’oggetto. Giacché ci siamo, è utile notare che neppure qualificando final il parametro Account del costruttore di Resource risolverebbe il problema. Il cloning ci viene di aiuto e dato che ogni buon sviluppatore Java conosce bene la differenza tra shallow copy e deep copy tratterò questi due casi separatamente senza soffermarmi troppo sul loro significato (si veda comunque la Figura 1) Shallow Copy Riprendiamo la classe Resource e modifichiamo il costruttore come segue: public Resource (String nome, Account acc) { … this.acc = (Account) acc.clone(); } public Account getAccount() { … return acc.clone(); }

Computer Programming n.139, Ottobre 2004

3

Software Design

PROGRAMMING

La “nativa” shallow copy effettua una copia bit a bit dei campi dell’oggetto in esame. Naturalmente per sfruttare questo meccanismo nativo dobbiamo rendere Cloneable la classe Account e implementare il metodo clone come segue: public Object clone(){ try { return super.clone(); } catch (CloneNotSupportedException e) { //This should not happen, since this class is Cloneable. throw new InternalError(); } } Stiamo assumendo che la classe Account utilizzi (per il suo stato) oggetti tutti clonabili. Riprenderemo questo punto più avanti quando parleremo del deep copy. In alcuni casi potrebbe essere più pratico mettere a fattor comune il metodo clone() in una apposita classe astratta per poi estenderla. Prima di chiudere con questa tecnica credo sia utile richiamare un altro aspetto spesso sottovalutato e causa di sottili errori. Consideriamo di aggiungere alla classe Resource un ulteriore costruttore che prenda in input un insieme di account ad esso associato (naturalmente esisterà il metodo getAccounts() che restituisce la lista degli account associati). Potremmo facilmente utilizzare un ArrayList, dunque: public Resource (String nome, ArrayList accs) { this.nome=nome; this.accs=(ArrayList)accs.clone(); } Un frammento di codice che utilizza queste classi potrebbe essere: Account acc1 = new Account ("Administrator"); Account acc2 = new Account ("Guest"); ArrayList accs = new ArrayList(); accs.add(acc1); accs.add(acc2); Resource res = new Resource ("PrinterHP",accs); ArrayList accs_ = res.getAccounts(); System.out.println("Resource "+res.getNome()+ " belongs to: \n" + (Account)accs_.get(0) +"\n"+ (Account)accs_.get(1)); acc1.setName("User"); System.out.println("Resource "+res.getNome()+ " belongs to: \n"+ (Account)accs_.get(0)+ "\n"+ (Account)accs_.get(1)); L’output risulta: Resource PrinterHP belongs to: Administrator Guest Resource PrinterHP belongs to: Computer Programming n.139, Ottobre 2004

4

Software Design

PROGRAMMING

User Guest Non proprio secondo le nostre aspettative. Cosa è successo? Semplicemente il clone di ArrayList funziona secondo shallow copy dunque è stata fatta una copia dei reference degli oggetti contenuti (questo vale anche per gli array). Nel prossimo paragrafo risolveremo il problema attraverso il deep copy. Deep Cloning Un modo semplice per risolvere il problema precedente è quello di utilizzare l’ereditarietà. Si costruisce una nuova classe derivata da ArrayList e si implementa il metodo clone che costruisce una nuova lista di elementi clonati. E’ un approccio poco percorribile (esempio di cattivo uso di ereditarietà) in quanto costringe a creare una classe artificiale utile solo per il cloning. Inoltre la classe Resource cambia interfaccia e questo impatta su tutti i client (violazione del Open/Closed Principle), e infine la sottoclasse non è sostituibile nei contesti in cui appare la superclasse poiché aggiunge metodi ad hoc. Un approccio migliore è quello del deep coping sulla classe Resource. Quando questa prende in input (dai costruttori) il parametro ArrayListt, si preoccupa di farne una copia “profonda” in un metodo private. Questo metodo potrebbe avere la seguente forma: private ArrayList clone(ArrayList al) { int size = al.size(); ArrayList newAL = new ArrayList (size); for (int i=0; i
View more...

Comments

Copyright ©2017 KUPDF Inc.
SUPPORT KUPDF