View Full Version : [JAVA] costruttori e metodi set incrociati
blackskop
08-01-2011, 17:53
Oggi sono incappato in questo problema:
Ho due classi A e B che inizialmente avevano i costruttori che prendevano come parametro il tipo dell'altra
public A(B istanza) { }
public B(A istanza) { }
volendole utilizzare in una terza classe capite subito che sarebbe impossibile.
Allora ho pensato di sostituire i costruttori con dei metodi set.
public class B { public void set(A istanza) { } }
public class A { public void set(B istanza) { } }
Questo mi risolve il problema iniziale ma ne introduce un altro:
ammettiamo che le due classi abbiano un altro metodo "execute" che al suo interno utilizza l'istanza dell'altra classe
public class B {
private A istanza;
public void set(A istanza) { this.istanza = istanza; }
public void execute() { istanza.test(); }
}
se io istanzio la classe B senza settare la classe A, nel momento in cui invoco execute di B viene sollevata una NullPointerException. Per ovviare potrei fare dei controlli all'interno di "execute" del tipo
public void execute() {
if (istanza != null) istanza.test();
}
ma se la classe B contenesse 2456 metodi set per altrettante classi, gestire la cosa con una serie di if mi sembrerebbe un obbrobrio (mi viene da pensare all'eventuale ridondanza degli if stessi da utilizzare in diversi metodi). A questo punto vi chiedo come posso forzare il programmatore ad usare i metodi set prima di invocare un qualsiasi altro metodo della classe? Il costruttore mi da esattamente questo ma c'è il problema dei riferimenti incrociati. :help:
blackskop
08-01-2011, 19:35
Una possibile soluzione di cui mi vergogno molto è lasciare i costruttori originari e inizializzare il tutto così:
A a = null;
B b = new B(a);
a = new A(b);
b = new B(a);
franksisca
08-01-2011, 19:45
scusa ma la domanda mi sorge spontanea....devi per forza avere questi "riferimetni" incrociati???
per risolvere il problema dei set chiamali semplicemente setA, setB, setC....
blackskop
08-01-2011, 20:10
scusa ma la domanda mi sorge spontanea....devi per forza avere questi "riferimetni" incrociati???
Si.
per risolvere il problema dei set chiamali semplicemente setA, setB, setC....
Cioè?
franksisca
09-01-2011, 00:07
sai benissimo che i nomi li definisci tu, io di solito uso questo "standard"
nomeMetodo
per i setters and getters
setNomeVariabile(Object nomeVariabile){...}
Object getNomeVariabile(){...}
quindi, nel caso di costruttore, basterebbe mettere setNomeClasse.
sono tutte cose ovvie, ma magari (mi ci sono trovato molte volte io) sono talmetne banali da sfuggirti...
blackskop
09-01-2011, 00:50
sai benissimo che i nomi li definisci tu, io di solito uso questo "standard"
nomeMetodo
per i setters and getters
setNomeVariabile(Object nomeVariabile){...}
Object getNomeVariabile(){...}
quindi, nel caso di costruttore, basterebbe mettere setNomeClasse.
sono tutte cose ovvie, ma magari (mi ci sono trovato molte volte io) sono talmetne banali da sfuggirti...
Non capisco come questo possa risolvere il problema. Puoi fare un breve esempio sulla falsariga del codice che ho postato prima?
secondo me ci deve essere un errore di progettazione, due classi distinte che non esistono l'una senza l'altra non hanno molto senso
blackskop
09-01-2011, 01:45
secondo me ci deve essere un errore di progettazione, due classi distinte che non esistono l'una senza l'altra non hanno molto senso
Beh se due oggetti devono comunicare tra loro devono per forza di cose avere una conoscenza reciproca.
puoi descrivere più o meno cosa devi fare?
blackskop
09-01-2011, 02:11
Nel mio caso specifico ho un JPanel che contiene svariati componenti e che si occupa soltanto di aggiornare questi componenti con delle informazioni che gli vengono passate da svariate classi che gestiscono la logica. Quindi dal JPanel vengono invocati i metodi nelle classi che gestiscono la logica che a loro volta invocano altri metodi nel JPanel.
public class JPanel {
private Manager1 manager1;
private Manager2 manager2;
private Manager3 manager3;
...
public void execute1() {
manager1.test();
manager2.test();
}
public void execute2() {
manager2.test();
manager3.test();
}
public void execute3() {
manager1.test();
manager3.test();
}
public void setManager1(Manager1 manager1) { this.manager1 = manager1; }
public void setManager2(Manager2 manager2) { this.manager2 = manager2; }
public void setManager3(Manager3 manager3) { this.manager3 = manager3; }
public void test1(String txt) {
;
}
public void test2(String txt) {
;
}
public void test3(String txt) {
;
}
}
class Manager1 {
private JPanel panel1;
public void execute() { panel1.test1("pippo"); }
public void setPanel1(JPanel panel1) { this.panel1 = panel1; }
public void test() { ; }
}
Se nel JPanel dimentico di fare un setManager, nel momento in cui invoco un execute di JPanel viene sollevata un'eccezione. Esiste un modo elegante per forzare il programmatore ad eseguire prima tutti i set prima dell'utilizzo degli altri metodi? Il problema si risolverebbe trasformando i set in costruttore però c'è il problema dei riferimenti incrociati e sinceramente la soluzione che ho trovato non è per niente elegante.
wingman87
09-01-2011, 03:05
Una delle due classi potrebbe "prevalere" sull'altra allocando essa stessa l'istanza dell'altra:
public A() {
this.b=new B(this);
}
public B(A istanza) { }
Anche se non sono sicuro che si possa fare una cosa del genere ma credo di sì.
non so se è una buona cosa passare il riferimento a un oggetto "in costruzione" (cioè, mi sembra di ricordare che non lo sia, mi sbaglio?)
banryu79
10-01-2011, 13:13
non so se è una buona cosa passare il riferimento a un oggetto "in costruzione" (cioè, mi sembra di ricordare che non lo sia, mi sbaglio?)
Ha rivelanza solo se la tua applicazione è multithreaded e le istanze di quelle classi vengono effettivamente utilizzate da più thread.
Altrimenti nessun problema.
Il this passato nel costruttore è un po' complicato perchè fila liscio, con o senza thread, a patto che, dove il "che" è seguito da un po' di fatti che riguardano l'accessibilità, diretta o indiretta, dei campi, sia nella classe che pubblica il this, sia nelle sue sottoclassi.
La questione è in verità molto semplice: quando pubblichiamo il this, chi lo riceve può accedere ai membri del tipo di quel this. Se i membri accessibili dipendono, direttamente o indirettamente, dal valore di un campo che nel costruttore "pubblicante" è inizializzato dopo la pubblicazione o il cui accesso è sovrascritto dal tipo runtime di quel this allora salterà fuori una null pointer exception, se siamo fortunati e il campo in questione è un riferimento, se è un primitivo siamo anche più nei guai.
esempio:
public class Prova {
public static void main(String[] args) {
A a = new A();
}
}
class A {
private String x;
A() {
B b = new B(this);
x = "hello";
}
String getX() {
return x.toString();
}
}
class B {
B(A a) {
String y = a.getX();
}
}
Al che uno dice, be', mica son fesso, x = "hello" lo metto prima. Va bene? Manco per idea.
public class Prova {
public static void main(String[] args) {
A a = new C();
}
}
class A {
private String x;
A() {
x = "hello";
B b = new B(this);
}
String getX() {
return x.toString();
}
}
class B {
B(A a) {
String y = a.getX();
}
}
class C extends A {
private String y;
C() {
y = "world";
}
@Override
String getX() {
return y.toString();
}
}
Beninteso, si può fare se si sta attenti. Ma, a conti fatti, è forse un'attenzione più onerosa di quella richiesta dal ricordardi si invocare il setter.
I due setter sono la soluzione più efficace. Non sono la migliore perchè giustamente si dice "così però non riesco ad obbligare il programmatore a invocare quel setter prima di usare i metodi". Giustissimo, preferibile ed encomiabile ma devi cambiare il design. Nota che quando due oggetti vogliono comunicare l'uno con l'altro essi lo fanno condividendo una parte di sè stessi, non tutto quanto, altrimenti cessano di essere due oggetti e al più sono un oggetto solo definito in due unità di compilazione diverse.
Ci sono tante soluzioni ma nessuna di quelle che mi vengono in mente sono così poco "invasive" come i setter.
blackskop
11-01-2011, 21:22
Il this passato nel costruttore è un po' complicato perchè fila liscio, con o senza thread, a patto che, dove il "che" è seguito da un po' di fatti che riguardano l'accessibilità, diretta o indiretta, dei campi, sia nella classe che pubblica il this, sia nelle sue sottoclassi.
La questione è in verità molto semplice: quando pubblichiamo il this, chi lo riceve può accedere ai membri del tipo di quel this. Se i membri accessibili dipendono, direttamente o indirettamente, dal valore di un campo che nel costruttore "pubblicante" è inizializzato dopo la pubblicazione o il cui accesso è sovrascritto dal tipo runtime di quel this allora salterà fuori una null pointer exception, se siamo fortunati e il campo in questione è un riferimento, se è un primitivo siamo anche più nei guai.
esempio:
public class Prova {
public static void main(String[] args) {
A a = new A();
}
}
class A {
private String x;
A() {
B b = new B(this);
x = "hello";
}
String getX() {
return x.toString();
}
}
class B {
B(A a) {
String y = a.getX();
}
}
Al che uno dice, be', mica son fesso, x = "hello" lo metto prima. Va bene? Manco per idea.
public class Prova {
public static void main(String[] args) {
A a = new C();
}
}
class A {
private String x;
A() {
x = "hello";
B b = new B(this);
}
String getX() {
return x.toString();
}
}
class B {
B(A a) {
String y = a.getX();
}
}
class C extends A {
private String y;
C() {
y = "world";
}
@Override
String getX() {
return y.toString();
}
}
Beninteso, si può fare se si sta attenti. Ma, a conti fatti, è forse un'attenzione più onerosa di quella richiesta dal ricordardi si invocare il setter.
I due setter sono la soluzione più efficace. Non sono la migliore perchè giustamente si dice "così però non riesco ad obbligare il programmatore a invocare quel setter prima di usare i metodi". Giustissimo, preferibile ed encomiabile ma devi cambiare il design. Nota che quando due oggetti vogliono comunicare l'uno con l'altro essi lo fanno condividendo una parte di sè stessi, non tutto quanto, altrimenti cessano di essere due oggetti e al più sono un oggetto solo definito in due unità di compilazione diverse.
Ci sono tante soluzioni ma nessuna di quelle che mi vengono in mente sono così poco "invasive" come i setter.
Eh si hai ragione, al limite l'unico modo più leggibile e manutenibile è farsi una classe intermedia che si occupi di associare i vari oggetti :\
Volendo cambiare il design cosa suggerisci? Non mi vorrei buttare in IoC e DI a colpi di spring :doh:
Tutto dipende dall'interpretazione che vuoi dare al problema. Ad esempio potremmo immaginare che la necessità dell'esistenza in A di un riferimento a B per l'esecuzione di talune operazioni sia rappresentata dall'esistenza di una parte di A indefinita fino all'arrivo di un B.
Puoi rappresentare questa parte indefinita con una classe interna. Supponiamo ad esempio che A sia:
class A {
private B b;
public void setB(B b) { this.b = b; }
public void operazione1SuB() { System.out.println(b.toString()); }
public void operazione2SuB() { System.out.println(b.hashCode()); }
public void operazioneNSuB() { System.out.println(b.getClass()); }
}
Non è possibile impedire a B di essere null ma è possibile evitare che le varie operazioni su B siano tentate senza verificarle una per una con la metafora della parte.
class A {
public class Operazioni {
Operazioni() { if(b == null) throw new IllegalStateException("b non è definito"); }
public void operazione1SuB() { System.out.println(b.toString()); }
public void operazione2SuB() { System.out.println(b.hashCode()); }
public void operazioneNSuB() { System.out.println(b.getClass()); }
}
private B b;
private Operazioni op;
public void setB(B b) { this.b = b; }
public Operazioni op() { return op == null ? op = new Operazioni() : op; }
}
In questo caso Operazioni è quella parte di A che non può esistere senza B e questa impossibilità è imposta durante l'esecuzione dal controllo nel suo costruttore. Di operazioni potrebbero essercene anche due, una che funziona quando c'è b e una che non funziona senza b.
Oppure potremmo pensare ad a come un operatore unario su valori di tipo b. Essendo unario, almeno un b bisogna passarglielo.
class A {
public void operazione1SuB(B b) { System.out.println(b.toString()); }
public void operazione2SuB(B b) { System.out.println(b.hashCode()); }
public void operazioneNSuB(B b) { System.out.println(b.getClass()); }
}
Oppure puoi usare dei listener. Se il pannello fa qualcosa quando altre istanze fanno altre cose è possibile immaginare che il pannello sia un ascoltatore degli eventi prodotti dalle altre istanze. Mentre non è possibile la dipendenza incrociata in costruzione (per una questione logica, è una reductio ad infinitum) è possibile che A sia un ascoltatore di B e B un ascoltatore di A:
import java.util.LinkedList;
import java.util.List;
public class Prova {
public static void main(String[] args) {
A a = new A();
B b = new B();
System.out.println("esecuzione senza 'set'");
a.operazione();
b.operazione();
a.add(b);
b.add(a);
System.out.println("esecuzione col 'set'");
a.operazione();
b.operazione();
}
}
class A {
private List<B> listeners = new LinkedList<B>();
void add(B b) { listeners.add(b); }
void operazione() {
for (B b : listeners) b.notifica(this);
}
void notifica(B b) {
System.out.println("A riceve una notifica di un evento prodotto da " + b);
}
}
class B {
private List<A> listeners = new LinkedList<A>();
void add(A a) { listeners.add(a); }
void operazione() {
for(A a : listeners) a.notifica(this);
}
void notifica(A a) {
System.out.println("B riceve una notifica di un evento prodotto da " + a);
}
}
Da un certo punto di vista è più solida perchè senza il set-add le cose funzionano lo stesso - semplicemente non capita nulla. Occorre però fare attenzione coi listener perchè si possono generare dei cicli (se una notifica di A generare un evento di B va in loop).
Con un po' di immaginazione si possono trovare anche altre soluzioni.
vBulletin® v3.6.4, Copyright ©2000-2025, Jelsoft Enterprises Ltd.