View Full Version : [Programmazione OO\Java] Chiarimenti su pattern creazionali Factory
e-commerce84
14-05-2010, 11:39
Ciao,
stò studiando i pattern ed avrei qualche dubbio che spero mi possiate chiarire.
Attualmente stò vedendo i pattern creazionali ed in particolar modo i Factory.
Faccio riferimento a questa slide: http://www.informatica.uniroma2.it/upload/2009/IS/07-Pattern%20GOF.pdf
Praticamente da quello che ho capito una factory è una speciale classe introdotta arbitrariamente (pure fabrication dei pattern GRASP) che ha la responsabilità di creare i miei oggetti e scelgo di introdurre questa nuova classe per fare creazioni complesse in oggetti coesi (per garantire high coesion e low coupling di fatto).
Vabbè ci sono vari tipi di Factory...per quanto riguarda la abstract factory tutto ok...
Mi stò impicciando non poco a capire cosa faccia invece la Lazy Factory...che praticamente dovrebbe usare la combinazione di un factory method e del memorizzare l'istanza creata su una mappa solo la prima volta e di poter riprendere la stessa istanza la volta successiva che si richede la stessa con certi parametri...fà questo esempio
import java.util.*;
public class Fruit {
/* Crea una HashMap comune a tutte le istanze della classe Fruit */
private static Map types = new HashMap();
private string type;
private Fruit(String type){ // COSTRUTTORE
this.type = type; // Assegna il parametro locale type
types.put(type, this); // Che fà esattamente ?!?!
}
/* Metodo statico comune a tutta la classe che dato un parametro type ritorna un
* oggetto di tipo Fruit */
public static Fruit getFruit(String type){
Fruit f;
if(types.containskey(type)){ // Se nella hashmap c'è il valore type
f = (fruit) types.get(type); // Mette quell'oggetto in f
}
else{
f = new Fruit(type); // Altrimenti lo crea con quel valore
}
return f; // Ritorna il riferimento ad f
}
}
Praticamente che fà? crea gli oggetti salvandoli di volta in volta nella hashmap e poi quando cerco un oggetto con certe specifici valori della variabile di istanza controlla se è presente nella hashmap?
A che mi serve esattamente un pattern del genere? quand'è che si usa all'atto pratico?
Alla riga types.put(type, this); fà l'inserimento nella hashmpa, praticamente gli stà dicendo di inserire lo specifico oggetto (this) relativo a quel valore type? Sapete darmi qualche info sulle hashmap? mai viste da praticamente nessun'altra parte :-/
Grazie
nuovoUtente86
14-05-2010, 11:48
L' inizializzazione lazy, tenta di ritardare la costruzione di un oggetto nel momento effettivo in cui occorre. Oltre a cio, nel tuo esempio, viene creata una mappa di singletono, in modo da ridurre l' utilizzo di memoria.
banryu79
14-05-2010, 12:03
Risposta parziale (i design pattern non sono il mio forte)
Praticamente che fà? crea gli oggetti salvandoli di volta in volta nella hashmap e poi quando cerco un oggetto con certe specifici valori della variabile di istanza controlla se è presente nella hashmap?
Sì, esatto.
Quando un certo 'type' viene richiesto tramite 'getFruit', viene restituita l'istanza creata e memorizzata nella mappa. Se quell'istanza nella mappa non c'è, la si crea.
Quindi per ogni 'type' richiesto esiste solo una istanza che viene condivisa.
Tenendo in conto questo fatto, e visto il contesto (parlavi di factory patterns) mi viene in mente che questo sistema possa servire per istanziare una sola volta una specifica factory concreta.
Alla riga types.put(type, this); fà l'inserimento nella hashmpa, praticamente gli stà dicendo di inserire lo specifico oggetto (this) relativo a quel valore type? Sapete darmi qualche info sulle hashmap? mai viste da praticamente nessun'altra parte :-/
HashMap è una implementazione dell'interfaccia Map, package java.util.
Fa parte del "Collections Framework" distribuito con il JDK standard.
Una Map è l'interfaccia ad un oggetto che mappa chiavi con valori.
Nel codice che hai postato non vengono usati i Generics, quindi non viene dichiarto il tipo esplicito che la mappa usa per la chiave (Key) e per il valore (Value) ma esaminando la chiamata a 'put' si vede che il primo argomento (key) è di tipo String e il secondo argomento (value) di tipo Fruit.
L'interfaccia Map fornisce tre "viste" che permettono di esaminare il contenuto di una Map come:
- un set delle chiavi contenute;
- una collezione dei valori contenuti;
- un set di coppie chiave-valore.
L'ordine della Map è definito dall'ordine con cui gli iteratori delle tre viste della mappa iterano gli elementi.
Alcune implementazioni (come TreeMap) grantiscono un certo ordine, altre (come appunto HashMap) no.
Si raccomanda fortemente l'uso di oggetti immutabili come chiavi di una Map (String per l'appunto è immutabile).
Comunque qui trovi un trail se vuoi approfondire: Java Collection Framework (file:///C:/Programmi/Java/jdk1.6.0_05/docs/technotes/guides/collections/index.html)
e-commerce84
14-05-2010, 12:16
Risposta parziale (i design pattern non sono il mio forte)
Sì, esatto.
Quando un certo 'type' viene richiesto tramite 'getFruit', viene restituita l'istanza creata e memorizzata nella mappa. Se quell'istanza nella mappa non c'è, la si crea.
Quindi per ogni 'type' richiesto esiste solo una istanza che viene condivisa.
Tenendo in conto questo fatto, e visto il contesto (parlavi di factory patterns) mi viene in mente che questo sistema possa servire per istanziare una sola volta una specifica factory concreta.
HashMap è una implementazione dell'interfaccia Map, package java.util.
Fa parte del "Collections Framework" distribuito con il JDK standard.
Una Map è l'interfaccia ad un oggetto che mappa chiavi con valori.
Nel codice che hai postato non vengono usati i Generics, quindi non viene dichiarto il tipo esplicito che la mappa usa per la chiave (Key) e per il valore (Value) ma esaminando la chiamata a 'put' si vede che il primo argomento (key) è di tipo String e il secondo argomento (value) di tipo Fruit.
L'interfaccia Map fornisce tre "viste" che permettono di esaminare il contenuto di una Map come:
- un set delle chiavi contenute;
- una collezione dei valori contenuti;
- un set di coppie chiave-valore.
L'ordine della Map è definito dall'ordine con cui gli iteratori delle tre viste della mappa iterano gli elementi.
Alcune implementazioni (come TreeMap) grantiscono un certo ordine, altre (come appunto HashMap) no.
Si raccomanda fortemente l'uso di oggetti immutabili come chiavi di una Map (String per l'appunto è immutabile).
Comunque qui trovi un trail se vuoi approfondire: Java Collection Framework (file:///C:/Programmi/Java/jdk1.6.0_05/docs/technotes/guides/collections/index.html)
Ti ringrazio sei satato molto chiaro, ora vado a dare un'occhiata a quel link per la gestione di collezioni...
Già che ci sono chiedo un'altra cosa per togliermi un altro dubbio...cosa intendi esattamente con: "mi viene in mente che questo sistema possa servire per istanziare una sola volta una specifica factory concreta." ?
Cioè...dici che potrebbe essere usato come factory di factory concrete? o ho capito male?
TNX
nuovoUtente86
14-05-2010, 12:30
Il metodo factory è quello statico getFruit che è l' unico in grado di istanziare oggetti di tipo Fruit, classe nella quale sono presenti solo costruttori privati.
Poi ovviamente puoi utilizzare lo stesso sistema anche per creare factory di altri altri factory, ma non è il caso in esempio.
banryu79
14-05-2010, 12:38
Il metodo factory è quello statico getFruit che è l' unico in grado di istanziare oggetti di tipo Fruit, classe nella quale sono presenti solo costruttori privati.
Poi ovviamente puoi utilizzare lo stesso sistema anche per creare factory di altri altri factory, ma non è il caso in esempio.
Già, hai ragione.
Una cosa che mi viene in mente, forse un'osservazione banale.
Se usi questo sistema per creare istanze che poi vengono condivise, è meglio (anche se non è obbligatorio) che gli oggetti creati siano immutabili.
Nel caso specifico poi (Fruit), devono proprio esserlo, per evitare che chi ottiene la data reference Fruit con la chiave "mela" mi faccia, ad esempio, un setType("pera"), con tutte le conseguenze del caso...
nuovoUtente86
14-05-2010, 13:34
E' necessario che gli oggetti siano immutabili: in maniera assoluta non è necessario, ma dipende dal caso particolare. E' fondamentale che però siano thread safe, cosa che il codice in esempio non è in quanto il "factory method" non implementa strumenti di sincronizzazione.
Cose fondamentali, oltre al thread-safe sono:
-non implementare clonable (o costruttori di copia profonda)
-non implementare serializable
-rendere il reference persistente
Le prime 2 impediscono di ottenere subdolamente istanze non univoche, il terzo di impedire al garbace collector di deallocare l' oggetto o la classe stessa.
banryu79
14-05-2010, 13:46
-non implementare clonable (o costruttori di copia profonda)
-non implementare serializable
-rendere il reference persistente
Le prime 2 impediscono di ottenere subdolamente istanze non univoche, il terzo di impedire al garbace collector di deallocare l' oggetto o la classe stessa.
Puoi chiarire il terzo punto?
Nel senso: nell'esempio proposto non mi pare ci sia bisogno di fare alcunchè, in merito, giusto?
Non appena viene richiesta una qualsiasi istanza di Fruit del tipo 'type' viene creata l'istanza e messa nella mappa (che è static, e private).
Da quel momento in poi l'istanza può solo essere referenziata da N reference al di fuori di Fruit, e anche se fossero zero resterebbe sempre referenziata dalla mappa, no?
nuovoUtente86
14-05-2010, 14:05
ma nessuno ti garantisce che il GC, sussitendo le condizioni, deallochi la classe con le sue componenti statiche.
In tal caso basta far referenziare la mappa da un runnable con il metodo run in wait().
banryu79
14-05-2010, 14:43
ma nessuno ti garantisce che il GC, sussitendo le condizioni, deallochi la classe con le sue componenti statiche.
Ah, bon ammetto un'assoluta ignoranza in materia.
Quali sarebbero, in due parole, queste condizioni?
In tal caso basta far referenziare la mappa da un runnable con il metodo run in wait().
Tecnica interessante :D
nuovoUtente86
14-05-2010, 16:01
Le classi e le interfacce vengono scaricate qualora il proprio classloader decida in tal senso oppure sia esso stesso soggetto al GC. Di norma le classi caricate dal bootstrap class loader non non soggette ad unload.
e-commerce84
14-05-2010, 17:41
Ragazzi,
ora vi faccio una domanda che non c'entra molto...ma...io ora stò studiando queste slide del corso...ok...le capisco ma mai mi verrebbero in mente tutte le cose che state tirando fuori voi due :muro: :muro: :muro:
E' una questione di esperienza sul campo che si acquisice con tempo e pratica o dite che c'è proprio da studiare anche altre cose?
nuovoUtente86
14-05-2010, 18:24
che corso di laurea frequenti?
e-commerce84
15-05-2010, 00:20
che corso di laurea frequenti?
Informatica
nuovoUtente86
15-05-2010, 01:36
Per quanto riguarda i pattern è importante il modo in cui è stato tenuto il corso di ingnegneria del software. Se è stato svolto in maniera adeguata dovresti avere la mente già predisposta a pensare, metodicamente, il software in maniera non monolita.
Per quanto riguarda le specifiche dei singoli linguaggi, bisogna andare a leggere la documentazione ufficiale disponibile.
^TiGeRShArK^
15-05-2010, 10:31
ma nessuno ti garantisce che il GC, sussitendo le condizioni, deallochi la classe con le sue componenti statiche.
In tal caso basta far referenziare la mappa da un runnable con il metodo run in wait().
che io sappia è inutile.
L'HashSet in questione verrà garbage-collected solo in caso la sua reference venga posta esplicitamente a null o la classe che lo contiene venga rimossa dal classloader manualmente o automaticamente nel caso di uscita dall'applicazione.
Non vedo il motivo per referenziare la mappa all'interno di un runnable in wait.
comunque riguardo a quel codice io lo scriverei così dato che imho sarebbe più chiaro:
import java.util.*;
public interface IFruit {
public String myMethod();
}
public class Apple implements IFruit {
public Apple() { }
public String myMethod { }
}
public class Orange implements IFruit {
public Orange() { }
public String myMethod { }
}
public class FruitFactory {
private static Map<String, IFruit> fruits = new HashMap<String, IFruit>();
public static IFruit getFruit(Class clazz) {
String type = clazz.getName();
return fruits.containsKey(type) ? fruits.get(type) : clazz.newInstance();
}
}
public class MainApp() {
public MainApp() {
Apple apple = FruitFactory.getFruit(Apple.class);
Orange orange = FruitFactory.getFruit(Orange.class);
}
}
Così si impediscono errori al runtime dovuti all'utilizzo delle stringhe e mi pare anche più chiaro quello che deve fare il codice.
nuovoUtente86
15-05-2010, 11:15
che io sappia è inutile.
L'HashSet in questione verrà garbage-collected solo in caso la sua reference venga posta esplicitamente a null o la classe che lo contiene venga rimossa dal classloader manualmente o automaticamente nel caso di uscita dall'applicazione.
Non vedo il motivo per referenziare la mappa all'interno di un runnable in wait.
Perchè solo l' utilizzo del bootstrap loader garantisce che l' istanza di tipo Class non entri nello scope del GC, ma non è detto che venga utilizzato il loader di default. Mentre un thread non sarà mai collected se il suo metodo run è ancora attivo.
^TiGeRShArK^
15-05-2010, 11:26
Perchè solo l' utilizzo del bootstrap loader garantisce che l' istanza di tipo Class non entri nello scope del GC, ma non è detto che venga utilizzato il loader di default. Mentre un thread non sarà mai collected se il suo metodo run è ancora attivo.
si, ma un user-defined classloader deve comunque alla fine richiamare i metodi del classloader della VM per poter caricare una classe...
Quindi, a meno della rimozione fatta esplicitamente sul classloader che avevo menzionato in precedenza mi sfugge perchè usando un custom class loader sia necessario utilizzare un runnable per mantenere attiva la reference della map....
nuovoUtente86
15-05-2010, 11:40
java.lang.ClassLoader è una classe astratta. La jvm ne possiede una implementazione di default, utilizzata qualora non venga esplicitamente utilizzato un classLoader customizzato.
Nessuno vieta di costruirsene uno a proprio piacimento.
Il GC, si premura solo di non scaricare le classi referenziate dal loader nativo, su tutto il resto può applicare i proprio algoritmi di pulizia.
^TiGeRShArK^
15-05-2010, 12:15
java.lang.ClassLoader è una classe astratta. La jvm ne possiede una implementazione di default, utilizzata qualora non venga esplicitamente utilizzato un classLoader customizzato.
Nessuno vieta di costruirsene uno a proprio piacimento.
Il GC, si premura solo di non scaricare le classi referenziate dal loader nativo, su tutto il resto può applicare i proprio algoritmi di pulizia.
Aspè, il garbage collector agisce solo sulle reference, e non gliene frega se le instanze di quella classe sono state create da un classloader di default o da uno personalizzato...
Nel caso di classi statiche esse verrano rimosse solo quando viene rimosso il corrispettivo classloader.
nuovoUtente86
15-05-2010, 12:21
Aspè, il garbage collector agisce solo sulle reference, e non gliene frega se le instanze di quella classe sono state create da un classloader di default o da uno personalizzato...
In questo caso stiamo parlando di variabili statiche, il cui ciclo di vita termina quando la classe (istanza di tipo Class) viene eliminato. Ciò avviene per 2 motivi: il ClassLoader di riferimento decide l' unload, opure il ClassLoader stesso è soggetto al GC perchè non più referenziato da nessuno. Questo non si applica quando ad essere utilizzato è il classloader primordiale.
^TiGeRShArK^
15-05-2010, 12:39
In questo caso stiamo parlando di variabili statiche, il cui ciclo di vita termina quando la classe (istanza di tipo Class) viene eliminato. Ciò avviene per 2 motivi: il ClassLoader di riferimento decide l' unload, opure il ClassLoader stesso è soggetto al GC perchè non più referenziato da nessuno. Questo non si applica quando ad essere utilizzato è il classloader primordiale.
No, non puoi specificare l'unload nel tuo classloader personalizzato, la classe verrà rimossa solo quando sei tu che decidi di rimuovere il classloader personalizzato.
Quindi alla fine non dipende dal tipo di classloader utilizzato ma solo quando sei tu che manualmente decidi di rimuovere la classe rimuovendo tutto il classloader.
nuovoUtente86
15-05-2010, 14:24
No, non puoi specificare l'unload nel tuo classloader personalizzato, la classe verrà rimossa solo quando sei tu che decidi di rimuovere il classloader personalizzato.
Si questo è corretto infatti sono stato io ad esprimermi male nel post precedente.
In realtà l' operazione manuale di unload (ottenibile anche scaricando esplicitamente il CL)non è fatta esplicitamente dal ClassLoader perchè non necessaria, in quanto le classi (non caricate per default) di cui nessuna istanza esiste posso essere gestite dal GC. Una stessa classe, caricata con CL differenti, viene infatti trattata come classi distinte.
nuovoUtente86
15-05-2010, 14:32
Inoltre allontanandoci dall' esempio di inizio thread, potrebbero esserci altri motivi per cui una variabile statica potrebbe essere eliminata dal GC. Ad esempio in un servlet container potrebbero essere compiute operazioni di reload dal motore di gestiore delle servlet.
Sant'Eufemio come la fate complicata :D.
1. c'è una sola condizione che determina l'unload di una classe. Una sola ed è sempre quella.
2. il runnable non funzionerebbe, per una questione di rami e compilatori molto furbi.
La questione della concorrenza c'entra e non c'entra nel senso che un singleton non ha alcuna particolarità rispetto alle normali questioni che sorgono in contesti concorrenti. Quando l'autore del thread studierà... i thread, vedrà problemi e soluzioni.
Deve essere immutabile? no, se io voglio un singleton mutabile che faccio, mi attacco al tram?
Circa lo scaricamento bisogna fare attenzione perchè è una di quelle cose un po' cervellotiche fatta in modo tale che il programmatore non se ne debba curare. Prima che sorga la questione dello scaricamento uno deve aver passato delle vere e proprio forche caudine (non basta un class loader ad hoc per rendere qualcosa scaricabile).
Nel dubbio, buttate sempre l'occhio alle specifiche
http://java.sun.com/docs/books/jls/third_edition/html/execution.html#12.7
nuovoUtente86
15-05-2010, 17:35
Sant'Eufemio come la fate complicata :D.
Circa lo scaricamento bisogna fare attenzione perchè è una di quelle cose un po' cervellotiche fatta in modo tale che il programmatore non se ne debba curare. Prima che sorga la questione dello scaricamento uno deve aver passato delle vere e proprio forche caudine (non basta un class loader ad hoc per rendere qualcosa scaricabile).
Nel dubbio, buttate sempre l'occhio alle specifiche
http://java.sun.com/docs/books/jls/third_edition/html/execution.html#12.7
Concordo sul fatto che la questione sia puramente accademica piuttosto che pratica.
Ne parla, in ogni caso, anche la sun con riferimento alle vecchie versioni del linguaggio.
http://java.sun.com/developer/technicalArticles/Programming/singletons/
2. il runnable non funzionerebbe, per una questione di rami e compilatori molto furbi.
Su questo non sono d' accordo, ma è ovviamente interessante indagare il perchè del fallimento della soluzione.
Mi riferivo a qualcosa del genere:
public class Persistenza implements Runnable {
private Set contenitore=new HashSet();
public synchronized void run(){
try{ wait(); } catch(InterruptedException e){}
}
public void aggiungi( Object o ){ contenitore.add(o ); }
public void rimuovi( Object o ){ ( contenitore.remove(o );
}
dove o è un oggetto singleton.
^TiGeRShArK^
15-05-2010, 18:06
Concordo sul fatto che la questione sia puramente accademica piuttosto che pratica.
Ne parla, in ogni caso, anche la sun con riferimento alle vecchie versioni del linguaggio.
http://java.sun.com/developer/technicalArticles/Programming/singletons/
Su questo non sono d' accordo, ma è ovviamente interessante indagare il perchè del fallimento della soluzione.
Mi riferivo a qualcosa del genere:
public class Persistenza implements Runnable {
private Set contenitore=new HashSet();
public synchronized void run(){
try{ wait(); } catch(InterruptedException e){}
}
public void aggiungi( Object o ){ contenitore.add(o ); }
public void rimuovi( Object o ){ ( contenitore.remove(o );
}
dove o è un oggetto singleton.
aspetta, fino alla versione 1.1 di java hai perfettamente ragione.
Ma dalla versione 1.2 (Java 2) in poi il problema è stato risolto, e ora siamo ben + avanti.
Avevo presupposto che stessimo parlando delle versioni attuali di java.
Per quanto riguarda le servlet (oddio, in effetti io ricordavo che avvenisse per le JSP, ma non posso escludere che avvenga anche per le servlet), in quel caso non ti devi preoccupare perchè c'è un classloader separato per ogni jar e quindi il classloader utilizzato è quello che caricherà tutta la tua applicazione e quindi ci riconduce paradossalmente al caso in cui si utilizzi il classloader di default in cui la classe statica verrà rimossa quando viene rimosso il classloader, che ovviamente corrisponde alla fine della tua applicazione.
Per cui non credo che all'atto pratico ci sia qualche situazione, con le versioni attuali di java, in cui la tecnica del runnable sia di qualche utilità....
ergo se attualmente la utilizzi a quanto ne so potresti risparmiarti righe di codice e (pochi) cicli di cpu dato che non dovrebbe cambiare nulla (a meno di qualche caso MOLTO particolare che ad ora mi sfugge e di cui ancora non abbiamo discusso).
...cmq non mi avete detto che ne pensate riguardo la mia rivisitazione del codice postato all'inizio del thread, se avete qualche proposta per migliorarlo.. :fagiano:
nuovoUtente86
15-05-2010, 19:09
Avevo presupposto che stessimo parlando delle versioni attuali di java.
Per quanto riguarda le servlet (oddio, in effetti io ricordavo che avvenisse per le JSP, ma non posso escludere che avvenga anche per le servlet), in quel caso non ti devi preoccupare perchè c'è un classloader separato per ogni jar e quindi il classloader utilizzato è quello che caricherà tutta la tua applicazione e quindi ci riconduce paradossalmente al caso in cui si utilizzi il classloader di default in cui la classe statica verrà rimossa quando viene rimosso il classloader, che ovviamente corrisponde alla fine della tua applicazione.
Sempre nel link in questione, sulla questione servlet, demanda sostanzialmente la questione al comportamento del servlet container
Per cui non credo che all'atto pratico ci sia qualche situazione, con le versioni attuali di java, in cui la tecnica del runnable sia di qualche utilità....
Presumibilmente, all' atto pratico, potrebbe essere uno zelo inutile, ma è anche interessante dare uno sguardo più ampio sulle cose.
...cmq non mi avete detto che ne pensate riguardo la mia rivisitazione del codice postato all'inizio del thread, se avete qualche proposta per migliorarlo.. :fagiano:
Trovo la soluzione molto orientata al factory method/abstract factory in quanto fornisce diverse concretizzazioni di una stessa interfaccia, piuttosto che customizzare, sulle esigenze del chiamante, instanze diverse di uno stessa classe.
Non mi piace, però, l' utilizzo del tipo Class come discriminate e delle classi concrete come tipizzazione dei reference nel metodo applicativo, o almeno non in quel modo (per quanto poi l' esempio è semplice):lo scopo di un factory è quello di celare la costruzione degli oggetti, garantendo un funzionamento trasparente (ad esempio un factory potrebbe fornire i codec per diversi formati. Il cliente del factory method deve essere in grado di riprodurre il file conoscendo solo l' interfaccia ritornata dal factory, senza aver bisogno di conoscere quale concretizzazione effettivamente lavora ), altrimenti se so quale classe istanziare (se espone un costruttore pubblico)
posso costruirmi l' oggetto da me (tralasciando ora possibili ottimizzazioni). Diverso ad esempio sarebbe il caso di una classe con costruttori privati, per cui si puo chiedere al factory di fare il lavoro per noi, possibilmente passandogli oltre al tipo richiesto, altri dati necessari alla scelta del costruttore corretto da invocare.
Per restare, ancora, sull' esempio avrei optato per l' utilizzo delle enumeration.
Su questo non sono d' accordo, ma è ovviamente interessante indagare il perchè del fallimento della soluzione.
Mi riferivo a qualcosa del genere:
...omissis...
dove o è un oggetto singleton.
Non vorrei complicare la vita ancora di più all'autore del thread per cui chiariamo subito che qui parliamo di enigmistica java, roba che se ti capita di incontrare una volta in trent'anni puoi dire di averle viste tutte.
Il fatto è che avete fatto un papocchio (termine tecnico), una specie di riassunto dell'altro thread lunghissimo thread su java in cui ne ho lette veramente di tutti i colori.
In buona fede ovviamente, direi nell'encomiabile desiderio di condividere del sapere quindi tanto di cappello.
Proviamo però a fare un po' di ordine, se ci state.
Prima di tutto quand'è che 'ste classi vengono scaricate. E qui già abbiamo un bel problema.
Il class unloading è opzionale. Che significa dire che il class unloading è opzionale, che conseguenze ha, che ce ne frega?
Dire che il class unloading è opzionale significa che qualsiasi programma il cui funzionamento dipenda dallo scaricamento di una classe è errato. Perchè potrei sempre eseguirlo su una piattaforma che non usa il class unloading e restare fregato.
Quindi noi che siamo bravi e lo sappiamo mai e poi mai useremo le proprietà del class unloading per far fare o non fare qualcosa al nostro programma.
Nel caso in cui una certa implementazione di Java supporti lo scaricamento allora lo scaricamento è vincolato ad una precondizione, vale a dire che è possibile solo se si verifica uno e un solo fatto:
che il class loader che ha caricato quella classe sia cestinabile dal garbage collector.
Una classe può essere scaricata se e solo se il suo class loader è cestinabile.
Questa è l'unica condizione, non ce ne sono altre.
Sempre specifiche alla mano, mi raccomando: è l'unico modo per essere quasi sicuri. Dico quasi perchè a volte ci sono dei bei rebus anche lì dentro.
Allora, in tanto la classe è scaricabile in quanto il suo class loader non sia più raggiungibile.
La raggiungibilità si determina in base al flusso di esecuzione di tutti i thread: dal momento in cui nessuno può più accedere ad un certo riferimento in poi l'oggetto riferito cessa di essere raggiunbile e può essere cestinato.
Naturalmente se fosse così facile saremmo qui coi calzoni corti il cerchio e la palla. L'implementazione è autorizzata ad eseguire tutte le trasformazioni del codice che non pregiudicano gli effetti collaterali derivanti dall'esecuzione delle istruzioni del programma nell'ordine in cui appaiono nel codice, secondo i diversi flussi di esecuzione.
Che vuol dire? In soldoni che se ho un riferimento che punta sempre ad un certo oggetto non posso dire per ciò stesso che quell'oggetto non sarà mai cestinato. L'implementazione può fregarmi quell'oggetto da sotto il naso. Quando può farlo? Tutte le volte in cui la presenza o l'assenza di quell'oggetto non hanno effetti collaterali.
Il runnable proposto da nuovoUtente86 fa difetto per una ragione simile. Lo anticipo ma sempre specifiche alla mano lo scaricamento di una classe è privo di effetti collaterali da cui consegue che un blocco di istruzioni il cui unico effetto sia quello di impedire lo scaricamento sia partimenti privo di effetti collaterali. Se ciò che non ha effetto è rimuovibile, il run del nostro Runnable potrebbe benissimo non essere mai eseguito.
Ripetiamo ancora: quando il class loader è cestinabile allora, e solo allora, saranno cestinabili le classi. Non c'è la possibilità di un comportamento diverso. se l'avessimo non sarebbe più il Java che conosciamo dalle sue specifiche.
Domanda: le specifiche dicono che in virtù di questa regola, il bootstrap loader risulta non essere mai cestinabile e quindi le classi che esso ha caricato sono parimenti eterne.
Le specifiche non dicono "le classi caricate dal bootstrap loader non sono scaricabili" nè dicono "il bootstrap loader non è cestianbile". Dicono che in virtù del fatto che le classi siano scaricabili solo quando il loro classloader sia cestinabile risulta che il bootstrap loader non sia cestinabile nè scaricabili le classi che esso abbia caricato.
Perchè?
nuovoUtente86
16-05-2010, 12:50
Effettivamente, le ottimizzazioni del compilatore/runtime possono rendere vano quel riferimento.
Il fatto che le classi caricate dal bootstrap loader non possano essere scaricate, dovrebbe dipendere dal fatto che il loader primordiale costituisce la root dell' albero di delegazioni e non ha parent se non l' istanza della JVM stessa. Mentre tutti gli altri loader definiti (che sono oggetti java comuni), ricevono (se non esplicitamente indicato) come genitore l' Application loader (che è un user-defined esso stesso) che poi è quello che carica, per default, le classi definite dall' utente.
Il senso è quello ma precisiamo che non sono le ottimizzazioni a cambiare le carte in tavolo, è proprio il linguaggio a dire che non si può legittimamente dedurre che capiti ciò che vorremmo usando quel runnable per quello scopo.
Circa il bootstrap loader non è un problema di delegati ma di raggiungibilità dei riferimenti. O il bootstrap loader è sempre raggiungibile (come dicono ad esempio le specifiche della JVM che però non sono parte delle specifiche del linguaggio) o esiste almeno un'istanza tra quelle caricate dal bootstrap loader che dobbiamo considerare sempre raggiungibile, il che è presumibilmente vero per Object di cui lo stesso boot strap loader, in quanto ClassLoader è istanza (sempre secondo il linguaggio). Propenderei per la seconda ipotesi.
Circa la controproposta di Tigershark, è problematica, il che è bello perchè offre un sacco di spunti di discussione.
Uno potrebbe dire che siccome i costruttori dei frutti sono pubblicamente accessibili non siano dei singleton: ne posso creare quanti ne voglio.
Il problema è che una classe Java con un costruttore privato non è estendibile (al di fuori della classe in cui è dichiarata) ma è comunque istanziabile a piacimento.
Non giurerei tuttavia che questa considerazione sia stata fatta.
Non è thread-safe ma la questione è irrilevante: non ci sono particolarità riguardo all'uso concorrente di un singleton rispetto a quanto varrebbe per qualsiasi altro pattern.
E' una factory? Per esserlo deve risolvere il problema dell'amorfismo dei costruttori. Io direi di no. E' vero che non ci sono "new" ma c'è una relazione di identità tra il tipo dell'istanza creata a la definizione della sua classe: se passo un Orange non c'è verso che possa darmi un sottotipo di Orange. Quindi si chiama Factory ma non è una factory.
C'è un poi il macignone:
clazz.newInstance()
newInstance() è sempre un problema in Java perchè non c'è uno strumento sintattico che costringa una classe ad avere un costruttore vuoto. Impediamo gli errori runtime dovuti alle stringhe per introdurne errori runtime di una specie diversa.
Si può invece obbligare un'istanza ad avere un metodo senza argomenti, quindi prima di arrenderci dovremmo provare con una cosa tipo:
interface Creator {
IFruit create();
public static IFruit getFruit(Creator c) {
usare c.create() che c'è per forza
}
Il che riporterebbe pure FruitFactory nell'alveo delle Factory (l'istanziazione diventa infatti polimorfica perchè è l'invocazione di un metodo che restituisce un valore in Java è un'espressione polimorfa).
Per il resto, il suo lavoro di contenitore di istanze condivise lo fa.
nuovoUtente86
16-05-2010, 19:56
Il problema è che una classe Java con un costruttore privato non è estendibile (al di fuori della classe in cui è dichiarata) ma è comunque istanziabile a piacimento.
Non giurerei tuttavia che questa considerazione sia stata fatta.
presumo tu ti riferisca all' utilizzo della reflection senza security manager.
Sì, il mitico setAccessible. Il security manager non ci aiuta perchè siamo di fronte ad una libreria. Forse si potrebbe fare qualcosa con le classi locali. Si può istanziare riflessivamente una classe locale? Non ho mai indagato. In teoria se non è sintetica si dovrebbe poter fare.
nuovoUtente86
17-05-2010, 11:30
però probabilmente creando un security manager ad-hoc, più restrittivo, si potrebbe neutralizzare il setAccessible.
Il problema è che è una libreria, non un programma. Io programmatore piglio la libreria VeryCleverSingletonFactory XYZ per usarla nel mio programma, se voglio posso sempre creare istanze dei singleton, anche se non dovrei.
Nulla di straordinario in sè, non è raro che un linguaggio abbia un limite rispetto alla capacità di rappresentare certi fenomeni.
Ma un problema di un certo interesse che ne sorge è: qual'è il limite dello sforzo che devo fare per aderire ad una certa rappresentazione nei casi in cui sia certa l'impossibilità di riprodurla esattamente?
Detto altrimenti, se un singleton non deve essere istanziabile al di fuori della factory e io so già che non potrò mai impedire in termini assoluti l'istanziabilità, ha senso star lì a disquisire di costruttori pubblici e privati?
A naso uno direbbe che la differenza tra un costruttore pubblico e uno privato vi sia, se non altro perchè il secondo non è invocabile salvo artifici riflessivi. E tuttavia il singleton non vuole qualcosa di "difficilmente istanziabile al di fuori di", vuole qualcosa di non istanziabile.
E' un caso di approssimazione irrazionale.
banryu79
17-05-2010, 12:42
Ma dotando la classe candidata a singleton di un campo privato statico da usare come counter delle istanze create e codificando nel costruttore privato un controllo su quel campo (che viene autoincrementato ad ogni invocazione del costruttore) che lancia un'eccezione (o peggio uccide la virtual machine :D) qualora il valore del campo sia maggiore di 1?
Ok, avevo voglia di scrivere la mia boiata :fagiano:
nuovoUtente86
17-05-2010, 13:10
L' utilizzo di unaa exception potrebbe tamponare il problema. Da valutare eventuali effetti collaterali su costruzioni lecite.
Funzionerebbe però è un controllo in esecuzione.
nuovoUtente86
17-05-2010, 13:14
Il problema è che è una libreria, non un programma. Io programmatore piglio la libreria VeryCleverSingletonFactory XYZ per usarla nel mio programma, se voglio posso sempre creare istanze dei singleton, anche se non dovrei.
Nulla di straordinario in sè, non è raro che un linguaggio abbia un limite rispetto alla capacità di rappresentare certi fenomeni.
Ma un problema di un certo interesse che ne sorge è: qual'è il limite dello sforzo che devo fare per aderire ad una certa rappresentazione nei casi in cui sia certa l'impossibilità di riprodurla esattamente?
Detto altrimenti, se un singleton non deve essere istanziabile al di fuori della factory e io so già che non potrò mai impedire in termini assoluti l'istanziabilità, ha senso star lì a disquisire di costruttori pubblici e privati?
A naso uno direbbe che la differenza tra un costruttore pubblico e uno privato vi sia, se non altro perchè il secondo non è invocabile salvo artifici riflessivi. E tuttavia il singleton non vuole qualcosa di "difficilmente istanziabile al di fuori di", vuole qualcosa di non istanziabile.
E' un caso di approssimazione irrazionale.
Premetto che non conosco l' argomento SecurityManager (per cui non colgo il discorso di cui sopra), però dalla documentazione del metoso setAccessible, si evince che lo stesso possa sollevare una SecurityException, derivante dal checkPermission del SecurityManager. Ecco perchè pensavo di intervenire, ipoteticamente, in quel senso.
Il ragionamento che fai è giusto: l'uso di setAccessible può essere ristretto dalle politiche di sicurezza.
L'obiezione è questa: le restrizioni di sicurezza sono un'opzione di esecuzione del programma.
Non ci sono restrizioni di sicurezza applicabili quando io programmatore vado ad usare una certa libreria.
Cioè io dico: metto questo costruttore a private così il programmatore che userà questa libreria non potrà istanziare la mia classe.
Tu prendi e usi setAccessible. Io che ho scritto la libreria posso obbligare il tuo uso di setAccessible a rilasciare un'eccezione di sicurezza? Direi di no. Non posso evitare che si verifichi ma neppure posso garantire che capiti.
nuovoUtente86
17-05-2010, 14:46
Si io ragionavo nell' ottica locale alla mio JVM, ma essendo il SM un componente di ogni singola macchina virtuale, ognuno utilizza le politiche che più gli aggradano.
Ipotizzo, sempre un conoscendo(se non di striscio l' argomento) i SM, ad un caso in cui si possa forzare l' utilizzo di un SM distribuito assieme alla libreria stessa. Detto in maniera banale e grossolana, al primo utilizzo del factory si installa il nuovo SM.
nuovoUtente86
17-05-2010, 14:50
L' utilizzo di unaa exception potrebbe tamponare il problema. Da valutare eventuali effetti collaterali su costruzioni lecite.
Mi autoquoto per aggiungere, che non può essere garantito che sia il factory per primo ad istanziare correttamente la "uniqueinstance" quindi ad acquisire quello che potremmo definire astrattamente un mutex, ma potrebbe essere creata la prima ostanza attraverso la rifessione.
vBulletin® v3.6.4, Copyright ©2000-2025, Jelsoft Enterprises Ltd.